题解:AT_arc137_d [ARC137D] Prefix XORs
对 \(a\) 数组作 \(k\) 次前缀和后得到的 \(a_n=\sum_{i=0}\binom{k+i-1}{i}a_{n-i}\)。
在贡献为异或情况下,若 \(\binom{k+i-1}{i}\mod2=1\),则对答案贡献 \(a_{n-i}\)。
用 Lucas 定理可得:
\(\binom{n}{m}\mod2=1\) 当且仅当 \(n\operatorname{bitand}m=m\),即二进制下 \(m\) 为 \(n\) 的子集。
于是我们写出一份暴力:
for(int i=1;i<=m;i++)
{
for(int j=0;j<n;j++)
{
if(((i-1+j)&j)==j){
ans[i]^=a[n-j];
}
}
}
我们把 \(i\) 改为从 0 开始,就可以得到一个 ((i+j)&j)==j,即二进制下 \(j\) 为 \(i+j\) 的子集。
观察这个东西我们发现其实它相当于 !(i&j),即二进制下 \(i\) 与 \(j\) 的交集为空。
证明:
假设二进制下 \(i\) 为 \(j\) 的交集不为空,则这个交集的最低位在 \(i+j\) 中将为 0,但 \(j\) 此位为 1,假设不成立。
根据这个结论我们可以枚举 \(i\) 的二进制的补集,暴力统计答案。
时间复杂度不会证,但跑极限数据不会超时。
const int MAXN=1e6+10;
int n,m,all;//all为二进制下与n位数相同的,每一位都为1的数
int a[MAXN];
signed main(){
scanf("%d%d",&n,&m);
all=(1<<__lg(n)+1)-1;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=0;i<m;i++)
{
int ans=a[n];//a[n]不会被枚举到
int p=~i&all;
for(int j=p;j;j=(j-1)&p)//枚举补集
{
if(j<n){
ans^=a[n-j];
}
}
printf("%d ",ans);
}
return 0;
}
然而我们发现这个暴力太慢了,很多点都跑到了 \(3s\) 以上。
发现这个暴力枚举补集其实可以 DP,直接枚举转移的二进制位。
时间复杂度 \(O(n\log{n})\)。
const int MAXN=2e6+10;
int n,m,lg,all;
int dp[MAXN];//要开两倍数组
signed main(){
scanf("%d%d",&n,&m);
lg=__lg(n);
all=(1<<lg+1)-1;
for(int i=1;i<=n;i++)
{
scanf("%d",&dp[n-i]);
}
for(int i=0;i<=lg;i++)//枚举转移的位
{
for(int j=1;j<=all;j++)
{
if(j&(1<<i)){
dp[j]^=dp[j^(1<<i)];
}
}
}
for(int i=0;i<m;i++)
{
printf("%d ",dp[~i&all]);
}
return 0;
}

浙公网安备 33010602011771号