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

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 long
const 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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值