【ZROI】【高维前缀和】【lucas定理】【17 提高 2】World Of Our Own

第3步a0^a1^a1^a2^a1^a2^a2^a3

第2步a0^a1^a1^a2 a1^a2^a2^a3

第1步a0^a1 a1^a2 a2^a3

第0步a0 a1 a2 a3

每次的合并操作相当于每个数往上走一步或是往左上走一步,那么,最终第 i i 个数在第j步走到位置0

(将原来1~n重新编号为0~n-1,这样可以方便计算)的方案数为 Cij C j i (在总共的j步中需要走i步向左上角的)。那么,我们会发现,一个数对答案有贡献只有当 Cij C j i 为奇数,即 Cijmod2=1 C j i m o d 2 = 1 ,考虑卢卡斯定理, Cijmod2=Ci/2j/2Cimod2jmod2mod2 C j i m o d 2 = C j / 2 i / 2 ∗ C j m o d 2 i m o d 2 m o d 2 ,相当于每次把i和j的二进制最后一位抓出来再删掉,只有当 jmod2=0,imod2=1 j m o d 2 = 0 , i m o d 2 = 1 时, Cimod2jmod2mod2=0 C j m o d 2 i m o d 2 m o d 2 = 0 ,所以当i为j的子集时,i对第j步的答案有贡献。

所以我们要做的就是计算j的所有子集的异或和,可以用高维前缀和。

计算前缀和有两种办法,一种是利用容斥,即

for(int i=1;i<=n;i++)
 for(int j=1;j<=m;j++)sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];

还有另一种方法

for(int i=1;i<=n;i++)
 for(int j=1;j<=m;j++)sum[i][j]=sum[i-1][j]+a[i][j];
for(int j=1;j<=m;j++)
 for(int i=1;i<=n;i++)sum[i][j]+=sum[i][j-1];

其中的第二种方法对于高维前缀和也是适用的。

如果要计算子集的前缀和,就可以将二进制数的每一位看成一维,然后计算高维前缀和。

代码如下:

for(int j=0;j<=log2(n);j++)
 for(int i=0;i<n;i++) if((i>>j)&1)a[i]^=a[i^(1<<j)];

时间复杂度: O(nlogn) O ( n l o g n )

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 8000006
#define LL long long
using namespace std;
int n,b,c,d,a[maxn];
LL ans;
int main(){
    freopen("C.in","r",stdin);
    freopen("C.out","w",stdout);
    scanf("%d%d%d%d%d",&n,&a[0],&b,&c,&d);int N=log2(n);
    for(int i=1;i<n;i++)a[i]=((LL)a[i-1]*a[i-1]%d+(LL)a[i-1]*b%d+c)%d;
    for(int j=0;j<=N;j++)
     for(int i=0;i<n;i++)
      if((i>>j)&1)a[i]^=a[i^(1<<j)];
    for(int i=0;i<n;i++)ans^=(LL)a[i]*((LL)i+1);
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值