题目
思路壹
一开始有这样一个思路,考虑每个数字最终会给哪些位置提供异或值。用 表示下标之差为 ,经过 次操作后,是否提供贡献。显然可以模仿快速幂,有
最后取模 ,因为异或只在乎奇偶性。这是卷积的式子,可以做到 ,所以计算 是 的。
有了这个,我们再模仿这个思路,想办法让原数组变成 ,就可以卷积了。拆位即可。复杂度 。
思路贰
经过别人的提醒,意识到 是一个组合数。为何?上面那个式子像不像是经典组合恒等式
我可以直接告诉你结论,
自己推式子怎么办?可以考虑递推式在 时的样子,即
再处理一下,应当为
这不就是一个杨辉三角吗?
我们只需要算组合数对 取模的结果就行了。根据卢卡斯定理, 当且仅当二进制表示下 是 的“子集”。
复杂度变成了 。
然后就 了。
代码
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
typedef long long int_;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void writeint(int x){
if(x > 9) writeint(x/10);
putchar((x%10)^48);
}
inline int qkpow(int_ b,int q,int m){
int ans = 1;
for(; q; q>>=1,b=b*b%m)
if(q&1) ans = ans*b%m;
return ans;
}
const int Mod = 998244353;
int omg[50]; // omg[x] = 3^{(Mod-1)/(2^x)}
int inv[50]; // inv[x]*omg[x] = 1
int pow2[50]; // pow2[x]*(2^x) = 1
void prepare_NTT(){
int g = Mod-1, cnt = 0;
while(!(g&1)) g >>= 1, ++ cnt;
omg[cnt] = qkpow(3,g,Mod);
inv[cnt] = qkpow(3,Mod-1-g,Mod);
for(int i=cnt; i>=1; --i){
omg[i-1] = 1ll*omg[i]*omg[i]%Mod;
inv[i-1] = 1ll*inv[i]*inv[i]%Mod;
}
pow2[0] = 1; // 2^0 = 1
g = pow2[1] = (Mod+1)>>1;
for(int i=2; i<=cnt; ++i)
pow2[i] = 1ll*pow2[i-1]*g%Mod;
}
const int MaxN = 400005;
int turn[MaxN];
void NTT(int a[],int n,int opt){
for(int i=1; i<(1<<n); ++i){
turn[i] = turn[i>>1]>>1;
if(i&1) turn[i] ^= 1<<n>>1;
if(i < turn[i])
swap(a[i],a[turn[i]]);
}
int logN = n; // 暂存
n = 1<<n; // 真正的长度
int *zxy = opt == 1 ? omg : inv;
for(int i=1; (1<<i)<=n; ++i){
int l = 1<<i>>1, w;
for(int *p=a; p!=a+n; p+=l*2){
w = 1; // 不能在外面赋初值!
for(int j=0; j<l; ++j){
int t = 1ll*w*p[j+l]%Mod;
p[j+l] = (Mod-t+p[j])%Mod;
p[j] = (p[j]+t)%Mod;
w = 1ll*w*zxy[i]%Mod;
}
}
}
if(opt == -1)
for(int i=0; i<n; ++i)
a[i] = 1ll*a[i]*pow2[logN]%Mod;
}
int f[MaxN], g[MaxN];
int a[MaxN], res[MaxN];
int main(){
// C(i+m-1,m-1)
prepare_NTT();
for(int T=readint(); T; --T){
int n = readint(), m = readint();
for(int i=0; i<n; ++i)
a[i] = readint();
int N = 0; // 实际上是存的 log
while((1<<N) < n) ++ N;
for(int i=0; i<(1<<N); ++i)
f[i] = ((i+m-1)&(m-1)) == m-1;
NTT(f,N,1);
// printf("f_ =");
// for(int i=0; i<(1<<N); ++i)
// printf(" %d",f[i]);
// putchar('\n');
for(int i=0; i<30; ++i){
for(int j=n; j<(1<<N); ++j)
g[j] = 0; // clear
for(int j=0; j<n; ++j)
g[j] = a[j]>>i&1;
NTT(g,N,1);
// printf("g_ =");
// for(int j=0; j<(1<<N); ++j)
// printf(" %d",g[j]);
// putchar('\n');
for(int j=0; j<(1<<N); ++j)
g[j] = 1ll*f[j]*g[j]%Mod;
NTT(g,N,-1);
for(int j=0; j<n; ++j){
res[j] -= res[j]&(1<<i);
if(g[j]&1)
res[j] ^= 1<<i;
}
}
for(int i=0; i<n; ++i){
writeint(res[i]);
putchar(' ');
}
putchar('\n');
}
return 0;
}
思路叁
看了这篇题解,心有所悟。
要注意到这个规律: 。也就是说,操作 次时,只有相差为 的倍数才会提供贡献。这相当于按照模 的余数分类,然后每一个做一次前缀异或和。
那么我们把 拆成二进制位。结束。复杂度 。(实际上是 才对。)