FWT
相比与 (NTT/FFT) 中的加法卷积,这里支持了位运算卷积
我个人不太清楚 (FWT/FMT) 的区别,还请赐教
inline void And(int *f,int lim,int opt){
for(reg int p=2;p<=lim;p<<=1){
int len=p>>1; for(reg int k=0;k<lim;k+=p){
for(reg int l=k;l<k+len;++l) if(opt==1) f[l]=add(f[l],f[l+len]); else f[l]=del(f[l],f[l+len]);
}
}return ;
}
inline void Or(int *f,int lim,int opt){
for(reg int p=2;p<=lim;p<<=1){
int len=p>>1; for(reg int k=0;k<lim;k+=p){
for(reg int l=k;l<k+len;++l) if(opt==1) f[l+len]=add(f[l+len],f[l]); else f[l+len]=del(f[l+len],f[l]);
}
}return ;
}
inline void Xor(int *f,int lim,int opt){
for(reg int p=2;p<=lim;p<<=1){
int len=p>>1; for(reg int k=0;k<lim;k+=p){
for(reg int l=k;l<k+len;++l){
f[l]=add(f[l],f[l+len]); f[l+len]=del(f[l],add(f[l+len],f[l+len]));
if(opt==-1) f[l]=mul(f[l],i2),f[l+len]=mul(i2,f[l+len]);
}
}
}return ;
}
Hdu5909
(Xor-FWT) 优化 (dp)
考虑维护子联通块的异或和的套路十分常见,直接对当前点和子树的信息 (Xor) 卷积即可
实现的时候可以先对每个点 (f[i][0]=1) 最后剪掉,同时每个点 (dfs) 之后不进行 (IFWT),来在归到父亲的时候减少复杂度
Codeforces662C
状压每一列,对于每个列的当前状态 (i),反转行的情况为 (S) 时最终状态为 (ioplus S)
那么列出来式子是一个 (Xor) 卷积,(FWT) 即可
实现的时候注意卷积数组的大小是 (2^n) 而不是乱开的 (5e5/1e6)
SNOI2017 遗失的答案
分解 (L,G) 质因数和次幂
一个合法的方案必然满足每个数次幂的 (min=G_p),最大的一个次幂是 (L_p)
观察到这个质因子个数很少,那么两位式状压:第一位表示是不是满足下界,第二位表示上界
这个状压很不套路,做前缀、后缀各一次 (dp) 得到信息
那么每个 (L) 的因数对其前后缀的信息做 (And-FWT) 取全集得到答案
实现的时候最后的每个因子要乘 (gcd)
Codeforces449D
题目本质是做一个背包然后求 (f[0])
这个式子是个 (And) 卷积,只会 (FWT),当然可以得到一个每次暴力卷的做法,但是复杂度很大
考虑这种 (dp) 的本质是某个物品如果有 (i) 个的话那么这个点的超集就会翻番(这和 (FWT) 的意思是一样的)
那么可以整体统计数之后 (FWT) 一次即可,在 (IDWT) 之前把每个 (A[i]) 替换成 (2^{A_i})
思考本质就能发现这个并不需要每次快速幂
统计答案记得减掉空集的情况,同时 (DWT) 的模数应为 (1e9+6)
uoj310 黎明前的巧克力
朴素的 (dp) 式子为 (f[i][j]=f[i-1][j]+2 imes f[i-1][joplus a_i])
和上面的一个 (cf) 题是一致的,这个式子是可以 (Xor-FWT) 优化的
但不一样的是这个题目中的系数变成了 (-1/3)
那么对于 (DWT) 出来的数组,解一元一次方程得到 (-1) 的个数 (k) 和 (3) 的个数 (n-k)(也就是必然会产生贡献)
那么用次幂替换掉之后 (IDWT) 即可
HAOI2015 按位或
需要 (Min-Max) 容斥的科技:
同时可以反过来写:
证明主要考虑分 (|T|) 的奇偶性把多余的都消掉剩下 (T={max(S)})
这题主要是考虑了应用到期望上的式子
最终求的就是 (E(U)) 那么现在没有的就是 (E(min(T)))
用封闭形式等等大力推推式子,得到 (E(min(T))=frac{1}{1-Sum(E(Uoplus T))})
剩下的就是子集和,(FWT) 即可
PKUWC2018 随机游走
首先 (min-max) 容斥转化为求集合最小值
这部分推导确实是新做法:设成两者相关的一次函数
即求到了 (S) 集合中的点的最小步数,把父亲的提出来,设 (f[t]=k_t imes f[x]+b_t)
推出来式子为 (k_x=frac1{d_x-sumk_x},b_x=frac{d_x+sumb_x}{d_x-sumk_x}),均与父亲节点的信息无关
那么对于每种取值集合都 (dfs) 一下得到 (b[rt])
接下来就是 (FWT) 求子集和了
子集卷积/集合幂级数
这个和普通的 (FWT) 的不同之处是中间要对集合的大小相加
那么进行 (FWT) 之后中间做加法卷积再 (IFWT) 即可
很不错的思想,也可能是我没有灵活转化的思维吧……
struct node{
int a[25];
void operator+=(const node &p){for(reg int i=0;i<=n;++i) a[i]=add(a[i],p.a[i]); return ;}
void operator-=(const node &p){for(reg int i=0;i<=n;++i) a[i]=del(a[i],p.a[i]); return ;}
node operator*(const node &p)const{
node ans; for(reg int i=0;i<25;++i) ans.a[i]=0;
for(reg int i=0;i<=n;++i) for(reg int j=0;i+j<=n;++j) ans.a[i+j]=add(ans.a[i+j],mul(a[i],p.a[j]));
return ans;
}
inline void print(){for(reg int i=0;i<4;++i) cout<<a[i]<<" "; puts("");}
}F[N],G[N];
inline void FWT(node *f,int lim,int opt){
for(reg int p=2;p<=lim;p<<=1){
int len=p>>1; for(reg int k=0;k<lim;k+=p) for(reg int l=k;l<k+len;++l) if(opt==1) f[len+l]+=f[l]; else f[l+len]-=f[l];
} return ;
}
signed main(){
n=read(); int lim=(1<<n); for(reg int i=1;i<lim;++i) bit[i]=bit[i>>1]+(i&1);
for(reg int i=0;i<lim;++i) F[i].a[bit[i]]=read();
for(reg int i=0;i<lim;++i) G[i].a[bit[i]]=read();
FWT(F,lim,1); FWT(G,lim,1); for(reg int i=0;i<lim;++i) F[i]=F[i]*G[i]; FWT(F,lim,-1);
for(reg int i=0;i<lim;++i) printf("%lld ",F[i].a[bit[i]]); puts("");
return 0;
}
THUPC2019 找树
其实 (FWT) 本质就是个求点值,转插值
先把题目转化为是判定每个 (i) 是否存在方案的计数问题,生成树问题启发我们使用矩阵树定理,位运算对应 (FWT)
那么对于每个加入的边,我们在 (v) 为第三维的数组中构造基尔霍夫矩阵,然后对每个 ((x,y)) 为第一第二维的数组做 (FWT)(因为边可能有重的)
考虑 (DWT) 就是把多项式转成点值,这里值得注意的是每个位上的运算就对应使用 (And/Or-FMT) 或者 (Xor-FWT)
再反过来对每个权值求行列式的值,最后 (IDWT) 回去得到存在性
其实就是求点值和做插值
复杂度 (Theta(n^3 2^w)),代码不难写
实现细节:行列式求值的时候模个大点的质数,因为我们只关心存在性
这题启发我们 (FWT) 也可以转化成点插值来理解,对于不同的运算,转成点值了也就有了独立性
同时 (FWT) 完全可以理解成多个生成函数的卷积,这样其实也可以推得模板的正确性
更多集合幂级数的题目参考
(这么多题真的不是人)