zoukankan      html  css  js  c++  java
  • 「学习笔记」FWT及集合幂级数入门

    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) 容斥的科技:

    [min(S)=sumlimits_{Tsubset S} (-1)^{|T|+1}max(T) ]

    同时可以反过来写:

    [max(S)=sumlimits_{Tsubset S} (-1)^{|T|+1}min(T) ]

    证明主要考虑分 (|T|) 的奇偶性把多余的都消掉剩下 (T={max(S)})

    这题主要是考虑了应用到期望上的式子

    [{E}(max(S))=sumlimits_{Tsubset S} (-1)^{|T|+1} E(min(T)) ]

    最终求的就是 (E(U)) 那么现在没有的就是 (E(min(T)))

    用封闭形式等等大力推推式子,得到 (E(min(T))=frac{1}{1-Sum(E(Uoplus T))})

    剩下的就是子集和,(FWT) 即可

    PKUWC2018 随机游走

    首先 (min-max) 容斥转化为求集合最小值

    这部分推导确实是新做法:设成两者相关的一次函数

    [f(S,x)=frac{sumlimits_{(x,y)in E} f(y)}{d_i}+1 ]

    即求到了 (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) 完全可以理解成多个生成函数的卷积,这样其实也可以推得模板的正确性

    好文 From Hs_black

    更多集合幂级数的题目参考

    题单

    (这么多题真的不是人)

  • 相关阅读:
    状压dp大总结1 [洛谷]
    集训日记(暑期第二周)【真正的集训,真正的考试】
    集训日记(暑期第一周)【6.22始】
    集训模拟赛3【啥也不会的一天】
    P2194 HXY烧情侣【Tarjan】
    6.28-集训模拟赛2【水爆但有进步的半天】
    Linux基础管道管理
    Linux基础进程管理优先级
    awk文本处理
    sed流编辑器
  • 原文地址:https://www.cnblogs.com/yspm/p/14359028.html
Copyright © 2011-2022 走看看