[BZOJ]4750: 密码安全 单调栈

来源:互联网 发布:生死狙击矩阵图片 编辑:程序博客网 时间:2024/06/03 20:50

Description

有些人在社交网络中使用过许多的密码,我们通过将各种形式的信息转化为 01 信号,再转化为整数,可以将这个人在一段时间内使用过的密码视为一个长度为 n 的非负整数序列 A_1,A_2,…,A_n 。一个人相邻几次在社交网络中使用的密码很有可能是类似的,这使得密码并不是足够安全。为了检验某些人在某些时间段内是否可能受到不安全的影响,我们需要计算上述序列的复杂程度。
这里写图片描述
的值,这将作为我们评估密码复杂程度的一个部分。由于答案可能很大,你只需要给出答案对10^9+61 取模的值即可。

题解:

本来听了scy的话,想去学一下单调栈,找到这题,发现最难学的不是单调栈,而是这个xor和的处理。首先看到这种题,思路一般是统计出每个数作为最大值对答案产生的贡献,所以我们用单调栈预处理出每个数左边第一个比他大的和右边第一个比他大的位置,维护一个栈底到栈顶单调递减的单调栈就好了。然后就是区间xor和的问题,我们都知道,若sum[i]表示a[1]a[i]的异或和,那么 a[l] xor a[l+1]....xor a[r]=sum[l1] xor sum[r],然后对于一个位置i,若a[i]是区间lr的最大值,那么这段区间的贡献就是a[i]乘上这个范围内的所有区间的异或和的和。针对异或和,我们可以预处理一个s数组,s[0/1][i][j]表示前j个数的sum在第i位上一共有多少个0或1,然后这个异或和就可以按位考虑了,对于一个位置i,若a[i]是区间lr的最大值,那么我们就看l1i1有多少sum这一位为0,ir有多少sum这一位为1,它们产生的这一位1就是它们的乘积,同理,左边的1和右边的0也可以产生1,把两个乘积加起来就是这一位有多少个1。

代码:

#include<bits/stdc++.h>using namespace std;#define LL long longconst LL mod=1000000061;const int Maxn=100010;int n,sta[Maxn],rs[Maxn],ls[Maxn];LL a[Maxn],sum[Maxn],s[2][32][Maxn];int main(){    int T;    scanf("%d",&T);    while(T--)    {        scanf("%d",&n);        sum[0]=0;        for(int i=1;i<=n;i++)scanf("%lld",&a[i]),sum[i]=sum[i-1]^a[i];        int top=0;        for(int i=1;i<=n;i++)        {            while(top&&a[sta[top]]<a[i])rs[sta[top--]]=i-1;            ls[i]=sta[top]+1;sta[++top]=i;        }        while(top)rs[sta[top--]]=n;        for(int i=0;i<=n;i++)        for(int j=0;j<30;j++)        s[0][j][i]=((!i)?0:s[0][j][i-1]),s[1][j][i]=((!i)?0:s[1][j][i-1]),s[(sum[i]>>j)&1][j][i]++;        LL ans=0;        for(int i=1;i<=n;i++)        {            for(int j=0;j<30;j++)            {                LL t1=(s[0][j][i-1]-((ls[i]==1)?0:s[0][j][ls[i]-2]))*(s[1][j][rs[i]]-s[1][j][i-1]);                //左边0右边1产生的1                 LL t2=(s[1][j][i-1]-((ls[i]==1)?0:s[1][j][ls[i]-2]))*(s[0][j][rs[i]]-s[0][j][i-1]);                //左边1右边0产生的1                 ans=(ans+((t1+t2)<<j)%mod*a[i]%mod)%mod;            }        }        printf("%lld\n",ans);    }}
原创粉丝点击