集合/二进制运算合集

RT,主要内容涉及有 高维前缀和(子集DP),高维后缀和,高维差分,快速沃尔什变换,子集卷积。

参考资料:

知识点合集

高维前缀和

用于求解 f(S)=TSg(T)

    for(int i=0;i<(1<<n);i++)f[i]=g[i];
    for(int j=0;j<n;j++){
        for(int i=0;i<(1<<n);i++)if((i>>j)&1)f[i]+=f[i^(1<<j)];
    }

本质原理:DP。统计依次前 j 物品出现的时候的贡献。

高维差分

考虑子集反演 f(S)=TSg(T)g(S)=TS(1)|S||T|f(T)

根据这个,其实可以考虑 FWT 进行计算,不过过于累赘。

我们发现这玩意在高维前缀和把加号换成减号容斥系数正好正确。

    for(int i=0;i<(1<<n);i++)g[i]=f[i];
    for(int j=0;j<n;j++){
        for(int i=0;i<(1<<n);i++)if((i>>j)&1)g[i]-=g[i^(1<<j)];
    }

快速沃尔什变换

应用于计算二进制卷积。

or 卷积

计算 Ci=(j|k)=iAjBk

我们定义 FWT(A)i=j|i=iAj

则有:

(FWT(A)·FWT(B))i=j|i=i,k|i=kAjBk=(j|k)|i=iAjBk=FWT(C)i

所以我们只需要考虑 FWT,IFWT 怎么求。

考虑钦定长度为 2n,使用分治进行计算。

定义将 A 的前 2n1 以及后 2n1 位划分为序列 A0,A1

不难发现 FWT(A)=merge(FWT(A0),FWT(A0)+FWT(A1))

IFWT 是逆运算,不难得到:IFWT(A)=merge(IFWT(A0),IFWT(A1)IFWT(A0))

(事实上,从FMT的视角来看,这玩意是高维前缀和)

and 卷积

类比 or,设计 FWT(A)i=(j&i)=iAj,也容易得到 FWT(C)=FWT(A)·FWT(B)

而根据分治法的结果,同样有:

FWT(A)=merge(FWT(A0)+FWT(A1),FWT(A0))

(高维后缀和)

IFWT(A)=merge(IFWT(A0)IFWT(A1),IFWT(A1))

xor 卷积

这个比较抽象,设 d(i)=popcount(i)mod2

FWT(A)i=j(1)d(j&i)Aj

根据 (j&i)(k&i)=(jk)&i

同样有:

i=jk(1)d(k&i)Bk(1)d(j&i)Aj=j,k(1)d(k&i)+d(j&i)AjBk=j,k(1)d(k&i)d(j&i)AjBk=j,k(1)d((jk)&i)AjBk=FWT(C)i

考虑计算 FWT(A)

容易发现 A0,A1 拼起来后,A1 的值作用于后半部分会导致最高位多出一个 1

所以有 FWT(A)=merge(FWT(A0)+FWT(A1),FWT(A0)FWT(A1))

因此有 IFWT(A)=merge(IFWT(A0)+IFWT(A1)2,IFWT(A0)IFWT(A1)2)

写法:考虑类比FFT,迭代换递归,但由于位置始终不变,所以不需要蝴蝶变换。

#include<bits/stdc++.h>
using namespace std;
#define N (1<<17)+5
const int p=998244353;
int n;
void ad(int &x,int y){
    x+=y;if(x>p)x-=p;if(x<0)x+=p;
}
void fwtor(int a[],int d,int tag){
    for(int len=1;len<(1<<d);len<<=1){
        for(int j=0;j<(1<<d);j+=(len<<1)){
            for(int k=0;k<len;++k)ad(a[j+k+len],a[j+k]*tag);
        }
    }
}
void fwtand(int a[],int d,int tag){
    for(int len=1;len<(1<<d);len<<=1){
        for(int j=0;j<(1<<d);j+=(len<<1)){
            for(int k=0;k<len;++k)ad(a[j+k],a[j+k+len]*tag);
        }
    }
}
void fwtxor(int a[],int d,int tag){
    for(int len=1;len<(1<<d);len<<=1){
        for(int j=0;j<(1<<d);j+=(len<<1)){
            for(int k=0;k<len;++k){
                int x=a[j+k],y=a[j+k+len];
                a[j+k]=1ll*(x+y)%p*tag%p;
                a[j+k+len]=1ll*(p+x-y)%p*tag%p;   
            }
        }
    }
}
int a[N],b[N],c[N],tmpa[N],tmpb[N];
int main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin>>n;for(int i=0;i<(1<<n);++i)cin>>a[i];
    for(int i=0;i<(1<<n);i++)cin>>b[i];
    memcpy(tmpa,a,sizeof a);memcpy(tmpb,b,sizeof b);
    fwtor(a,n,1);fwtor(b,n,1);
    for(int i=0;i<(1<<n);i++)c[i]=1ll*a[i]*b[i]%p;
    fwtor(c,n,-1);
    for(int i=0;i<(1<<n);i++)cout<<c[i]<<" ";cout<<"\n";
    memcpy(a,tmpa,sizeof a);memcpy(b,tmpb,sizeof b);
    fwtand(a,n,1);fwtand(b,n,1);
    for(int i=0;i<(1<<n);i++)c[i]=1ll*a[i]*b[i]%p;
    fwtand(c,n,-1);
    for(int i=0;i<(1<<n);i++)cout<<c[i]<<" ";cout<<"\n";
    memcpy(a,tmpa,sizeof a);memcpy(b,tmpb,sizeof b);
    fwtxor(a,n,1);fwtxor(b,n,1);
    for(int i=0;i<(1<<n);i++)c[i]=1ll*a[i]*b[i]%p;
    fwtxor(c,n,(p+1)>>1);
    for(int i=0;i<(1<<n);i++)cout<<c[i]<<" ";cout<<"\n";
}

注意到 IFWT Xor 我们可以最后再除掉 2d 也是可以的

注意到三个卷积的形式其实是一样的,也就是说我们会有:

  1. FWT(kiFi(x))=kiFWT(Fi(x))

  2. Fi(x)=IFWT(FWT(Fi(x)))

    前面的乘法是做异或卷积,后面是对位相乘

子集卷积

CS=TSATBST

考虑到这等价于:

Ci=j&k=0,j|k=iAjBk

j&k=0 可以描述为 popcount(j)+popcount(k)+popcount(j&k)=popcount(j|k)

这里考虑 or 卷积的 FWT。

所以设 FWT(A,I)i=j|i,popcount(j)=IAj

则有 FWT(C,I)=JFWT(A,J)FWT(B,IJ)

然后 IFWT 回去得到 CI,则 CS=C|S|[S]

O(n22n)

#include<bits/stdc++.h>
using namespace std;
int a[20][1<<20],b[20][1<<20],c[20][1<<20];
#define bit __builtin_popcount
const int p=1e9+9;
void ad(int &x,int y){
    x+=y;if(x>p)x-=p;
    if(x<0)x+=p;
}
void fwtor(int a[],int n,int tag){
    for(int len=1;len<(1<<n);len<<=1)for(int j=0;j<(1<<n);j+=len<<1)for(int k=0;k<len;k++)ad(a[j+k+len],a[j+k]*tag);
}
int read(){
    int x=0;char ch=getchar();
    while(ch>'9'||ch<'0')ch=getchar();
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return x;
}
void print(int x){
    if(x<10)putchar(x+'0');
    else print(x/10),putchar(x%10+'0');
}
int main(){
    int n=read();
    for(int i=0;i<(1<<n);i++)a[bit(i)][i]=read();
    for(int i=0;i<(1<<n);i++)b[bit(i)][i]=read();
    for(int i=0;i<=n;i++)fwtor(a[i],n,1),fwtor(b[i],n,1);
    for(int k=0;k<=n;k++)for(int i=0;i<=k;i++)for(int x=0;x<(1<<n);x++)ad(c[k][x],1ll*a[i][x]*b[k-i][x]%p);
    for(int k=0;k<=n;k++)fwtor(c[k],n,-1);
    for(int i=0;i<(1<<n);i++)print((c[bit(i)][i]%p+p)%p),putchar(' ');
}

例题

ABC212H

模版题,可以容斥计算。

F(x)=xAi

[x0]i=1NFi(x)=[x0]IFWT(i=1NFWT(Fi(x))=[x0]IFWT(FWT(i=1NFi(x)))=[x0]IFWT(FWT(ixit=1Nfit))

等比数列计算最后 IFWT 回去即可。

注意特判 fi=1

P5387 人形演舞

博弈论结合FWT,一般用于求方案数,可以先求出每个子游戏的 sg 函数。

打表看 sg 函数,发现 sgx=x+12log2xsg0=0

至于它要选出 |V| 个,本质上是求:

F(x)=i=1mxsgi,求 F|V|(x) 的非项系数之和。

求出 FWT 之后再快速幂,然后 IFWT 即可。

州区划分

注意不连通是合法的

首先求出 szS=xSwx,设 gS=[vaild(S)]szSp,至于不合法当且仅当图连通且具有欧拉回路,根据度数判断即可。

考虑dp,设 fS 为当前已经用了的点的集合为 S,显然有:

fS=1szSpTSfTgS/T

考虑如何使用 fwt 优化稍加改动的子集卷积

注意到子集卷积的每一步有一个 |S|,可以先做普通子集卷积,在做到 |S| 的时候将相应状态除掉 szSp 再将 |S| 的部分加入卷积即可。

code

And-Or Game

题意:给定值域为正整数的集合 A,B,你最初有一个数 V=0,每次可以执行下列两种操作:

  1. 选择 xA,VVx,
  2. 选择 yA,VVx

你可以进行任意次操作,求可能达到的 V 的个数。

首先转化为表达方案数,我们合并连续的使用 A/B 的操作。也就是 A,B 自卷 |A|,|B|,可以用 FWT 配合快速幂完成。

然后我们现在就是两个操作交替进行了,那么考虑一个答案是如何生成的,显然是在 or 时至少让某位的 0->1,在 and 时至少让某位 1->0,并且不会被抵消。

这就告诉我们最多不超过 logV 次操作运算就封闭了,暴力交替操作判断操作前后可取数集合是否相同即可。

O(Vlog2V)​,但跑不满。

code

CF582E

题面较长自行阅读。

根据输入显然知道要建立表达式树。直接用栈建就行了。

然后考虑我们设 fi,j 为在子树 i 里,n 次操作的运算结果二进制压缩后是 j

则可以预处理出单独每个字符的取值,在叶子节点的时候赋值。当如果是 ? 那就直接加上所有。

然后合并子树根据运算符类型用 fwtor fwtand 优化合并即可。问号就齐上。

二进制卷积优化DP转移

CF1326F2

很好的数学,使我的大脑旋转。

n18,给定 n×n 矩阵 ai,j,保证 ai,j=aj,i,考虑每个 1n 的排列 p,定义 01ssi=api,pi+1,|s|=n1。问对于 2n101 串,生成其的排列个数各有多少。

注意到关键限制是 ai,j 的值,直接做很困难。

考虑子集反演,本质上只考虑钦定某些位置为 1,忽略掉 0 的限制。 设原本答案为 ansi,设 fi=i|j=jansj

ansi=j|i=j(1)(popcount(ji))fj

O(n2n)

考虑计算 f。一个奇怪的发现是:对于 01 串的所有极长 1 段(带上前面的那个 0),将其整体移动到其他 0 的前面,不会使得这一段 1 变成零,而有可能使得 00 撞在一起形成新的 1

这告诉我们,所有 fi,在 i 的二进制表示下,所有极长 1 段带上前面的 0 形成的长度可重集相同(我们默认第 1 位,也就是 p1,是零)的时候,答案相同。注意这个结论

也就是例如 f(01110110)=f(01100111)=f(11011100),其可重集为 {1,1,3,4},注意有个“不存在的 0”。

由此,我们发现答案至多有 18 的整数划分种,这玩意好像是三百多,约等于 182

所以呢?这就有点子集卷积的味道了。

考虑对于每个 1 段计算答案。

dpS,i 为经过 S 中所有点,最终停在 i,且满足所有路径的边均为 1 的路径条数。

显然 dpS,i·ai,kdpS{k},k,dp{i}i=1

然后对 n 的每种整数划分求答案。

其实这个整数划分对应的是子集卷积,只不过钦定加入大小为多少的子集而已。

根据 FWT 优化子集卷积,可以先对所有大小的 |S| 进行 FWTOR

然后在整数划分的过程中进行子集卷积,最后我们只关心全集的值,可以直接子集反演掉(FWTOR 本质是子集求和,这里逆差分就行)。

在求出每种情况的值之后就求得了 f,然后反演回 ans 即可。

高维前缀和,高维差分,高维后缀和,FWTOR,FWTAND,FMTOR,FMTAND 本质与子集反演都是相通的。

code
#include<bits/stdc++.h>
using namespace std;
#define N 18
#define int long long 
// const int p=998244353;
#define bit __builtin_popcount
int f[(1<<18)+5],n,m,dp[(1<<18)+5][18],c[19][(1<<18)+5],dep,g[19][(1<<18)+5];
int rch[N];
void fwtor(int a[],int n,int tag){
    for(int len=1;len<(1<<n);len<<=1){
        for(int j=0;j<(1<<n);j+=len<<1){
            for(int k=0;k<len;k++){
                a[j+k+len]+=a[j+k]*tag;
                // a[j+k+len]%=p;
            }
        }
    }
}
map<vector<int> ,vector<int>>h;
vector<int>now;
void dfs(int sur,int lst){
    if(!sur){
        /*考虑到FWT or 的本质是高维前缀和,我们只要h[(1<<n)-1]那么这里做子集反演*/
        int res=0;
        for(int i=0;i<(1<<n);++i){
            if(bit(((1<<n)-1)^i)&1)res-=g[dep][i];
            else res+=g[dep][i];
        }
        // cout<<res<<"\n";
        for(auto i:h[now]){
            f[i]+=res;
            // cout<<" "<<i;
        }
        // cout<<"\n";
        return ;
    }
    for(int i=1;i<=min(lst,sur);i++){
        for(int j=0;j<(1<<n);j++)g[dep+1][j]=g[dep][j]*c[i][j];
        ++dep;now.push_back(i);
        dfs(sur-i,i);
        --dep;now.pop_back();
    }
}
signed main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);    
    cin>>n;
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            char x;cin>>x;
            if(x=='1')rch[i]|=(1<<j);   
        }
    }
    for(int i=0;i<n;i++)dp[1<<i][i]=1;
    for(int i=0;i<(1<<n);i++){
        for(int j=0;j<n;j++)if((i>>j)&1)for(int k=0;k<n;k++)if(((i>>k)&1)^1){
            if((rch[j]>>k)&1)dp[i|(1<<k)][k]+=dp[i][j];    
        }
    }
    for(int i=0;i<(1<<n);i++)for(int j=0;j<n;j++)c[bit(i)][i]+=dp[i][j];
    for(int i=0;i<=n;i++)fwtor(c[i],n,1);
    for(int i=0;i<(1<<n-1);i++){//2^{n-1}种01串
        vector<int>now;
        int c=1;
        for(int j=0;j<n-1;j++){
            if((i>>j)&1)++c;
            else now.push_back(c),c=1;
        }
        now.push_back(c);sort(now.begin(),now.end());reverse(now.begin(),now.end());h[now].push_back(i);
    }
    for(int i=0;i<(1<<n);i++)g[0][i]=1;
    dfs(n,n);
    for(int i=0;i<n-1;i++)for(int j=0;j<(1<<n-1);++j)if((j>>i)&1)f[j^(1<<i)]-=f[j];
    for(int i=0;i<(1<<n-1);i++)cout<<f[i]<<" ";
}

ABC367 G

神奇题目

场上想到了引入多元生成函数之后就嗝屁了。

定义两个多项式的运算 A(z)B(z)=ijzijaibj,也就是异或卷积。

定义两个二元生成函数 A(x,y)B(x,y)=i,pj,qxijyp+qai,pbj,q

我们仍然选用 计算连续的

显然 k 很大,我们需要求出每个 xor 和的出现次数 ci

ci 可以简单的表示为:

C(z)=i=1zi(d=1dmN[xiydm]i=1n(1+xaiy))

现在问题在于计算 (1+xaiy)

这其实是 FWT ,而我们考虑计算其卷积,最后 IFWT

如果我们先不考虑 y 的影响,那么有 FWT(1+xai)=FWT(1)+FWT(xai) 计算结果中的第 j 项系数为 2[d(ai&j)=0],也就是二进制 & 有偶数个 1 的位置为 2,否则为 0

根据这个我们可以计算 (1+xai),具体的我们考虑一个 DP fi,0/1 表示所有数中 &i 有偶数/奇数个 1 的数的个数,还原每一项即可。事实上有 fi,0+fi,1=N

但是带上了 y 怎么办。实质上是把 ydm 的项拿出来。对于 xi 而言,如果 d(i&j)=0 ,那么就有选它,值增加一,或者不选它,如果 d(i&j)=1,就有选它,值减少一,或者不选它。选了的话选了的个数就会增加。

y 的实质是记录选了的个数,所以对应了 (1+y),(1y) 的两个决策。

所以实际上拿出 xiy 的系数是一个多项式 (1+y)fi,0(1y)fi,1。甚至我们可以据此定义广义的 FWT(第一维 xor,第二维多项式积)

FWTex(F(x,y))=zji(1)d(i&j)fi,kyk

显然我们还是得到了一个二维多项式,且可以有 FWTex(1+xaiy)=zk+zk(1)d(k&ai)y

也可以佐证上述结论。

所以我们对于每个 xi,拿出 ydm 的系数后就可以做 IFWT 得到 C(z) 了。

怎么拿?不妨设 dp1i,j(1+y)iyk,kmodm=j 的系数,dp2i,j(1y)iyk,kmodm=j 的系数。

那么 dp1fi,0,jdp2fi,1,(j)modm 就是所求系数。

code

ZJOI2019 开关

考场压根没有想 FWT

首先还是把 pi 统一为概率,也就是 pi=1

fSai=[iS] 的答案,显然有:

{S=f=0SfS=1+pifS{i}

考虑 FWT。

F(x)=fSxS,G(x)=pixi,显然有 G(1)=1

有:

fS=1+pifS{i}F(x)=xi+G(x)×F(x)+k·x

注意这里的 × 代指异或卷积,且 xi i 的取值是空集到全集,由于 f 是不遵这个规则的所以需要插入修正常数

既然有异或卷积定然考虑 FWT,不妨设 F(x)=FWT(F(x)),G(x)=FWT(G(x))

F(x)=FWT(xi)+F(x)·G(x)+FWT(kx)

根据 FWT 定义,有 [xS]F(x)=(1)|ST|[xT]F(x)

{[xS]FWT(xi)=[xS]FWT(xi)=(1)|Si|=2n|S|SS(1)|S|=2n[S=][xS]G(x)=[xS]piFWT(x{i})=(1)|S{i}|pi=12iSpi[xS]FWT(k·x)=k

所以有

[xS]F(x)(1G(x))=2n[S=]+k=[xS]F(x)iS2pi

由于 S= 时,可以得到后式为零,因此有 k=2n

考虑 IFWT 回去,但是我们需要特别处理空集,有:

F(x)=[xS]IFWT(F(x))=T(1)|ST|[xT]F(x)2n=[x]F(x)2n+T(1)|ST|[xT]2n[T=]2n2iTpi2n=[x]F(x)2n+T(1)|ST|[xT]2n2iTpi2n

又,我们需要知道 [x]F(x) 的值,但有 [x]F(x)=f=0,所以有:

[x]F(x)=T[xT]2n2iTpi

回带有:

F(x)=[x]F(x)2n+T(1)|ST|[xT]2n2iTpi2n=T[xT]2n2iTpi2n+T(1)|ST|[xT]2n2iTpi2n=T((1)|ST|1)2n2iTpi2n=|ST|mod2=11iTpi

后面容易背包 O(nV) 解决。

posted @   spdarkle  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示