集合幂级数
位运算卷积
对于长度为\(2^n\)的数列(下标\([0,2^n-1]\))\(A,B\),定义 \(C_i=\sum_{j \bigoplus k=i}A_j\times B_k\),其中 \(\bigoplus\) 是位运算,该操作被称作位运算卷积,记作 \(C=A\bigoplus B\).
因为 \(FFT\) 的思想是将 \(A,B\) 转化为点值表示法,相乘之后再转成多项式,所以我们可以类似地,寻找一个变换 \(FWT(A)\),使得\(FWT(C)=FWT(A)FWT(B)\),这里是点乘,同时我们希望 \(FWT(A)\)是一个线性变换,也就是说,\(FWT(A+B)=FWT(A)+FWT(B),kFWT(A)=FWT(kA)\)。
不妨设 \(FWT(A)_i=\sum_{j=0}^{2^n-1}f(i,j)A_j\),也就是第 \(j\) 位对第 \(i\) 位的贡献系数为 \(f(i,j)\) 。
那也就是说,
同时,因为 \(A \bigoplus B=C\),
即 $$C_p=\sum_{j\bigoplus k=i}A_jB_k$$
对比系数,可得 \(f(i,j)f(i,k)=f(i,j\bigoplus k)\)
同时,可以发现我们上述的推导没有用到任何位运算的性质,也就是说,普通的多项式卷积也符合上述条件,即 \(x^jx^k=x^{j+k}\)。
不过就算我们现在有了 \(f\) 依然不能快速计算 \(FWT(A)\)。
我们不妨给 \(f(i,j)\) 加上更强的限制,让 两个数的 \(f\) 等于他们每个二进制位上两个数的 \(f\) 的乘积。
那我们考虑分治计算 \(FWT(A)\) ,.
其中 \(c\) 是 \(i\) 的最高位,\(i',j'\)是 \(i,j\)去掉最高位之后的数。
容易发现按位去做就可以变成一个子问题了(看看代码就会了)。
那么我们求出 \(FWT(C)\) 之后,只需要逆用 \(FWT(C)\),也就是被称为 \(IFWT(C)\) 就好了,和上述过程几乎一模一样,只是 \(f\) 这个矩阵变成了原本的逆矩阵就好了,这也就是说,位运算卷积适用的情况就是 \(f(i,j)f(i,k)=f(i,j\bigoplus k)\),且矩阵 \(f\) 有逆矩阵。
基础位运算卷积
或卷积
\(C = A | B\)
注意到 \(j\) 是 \(i\) 的子集且 \(k\) 是 \(i\) 的子集等价于 \(j|k\) 是 \(i\) 的子集。
也就是说,构造 \(f(a,b)=[a|b=a]\),也就是说矩阵为:
我们可以直接得出 \(f\) 的逆矩阵:
与卷积
\(C = A \& B\)
仿照上面构造 \(f(a,b)=[a|b=b]\)
矩阵为:
逆矩阵:
异或卷积
需要一点点观察技巧,构造 \(f(a,b)=(-1)^{|a\& b|}\),这是因为 \((-1)^{|i\& j|}(-1)^{|i\& k|}=(-1)^{|i\& j|+|i\& k|}\),注意到如果某一位\([i\& j]=[i\& k]\),没有贡献,也就是说,否则只要 \(i\) 这一位 是 1 就有贡献,因此 等价于 \(-1^{|i\& (j \bigoplus k)|}\)。
矩阵为:
逆矩阵:
\(\text{K}\) 进制卷积
求 \(FWT(A)\)的部分是类似的,重点还是构造 \(f\).
高维 max 卷积
也就是每一位取 \(max\)。
类似地,设 \(f(a,b)=[max(a,b)=a]\),显然是符合要求的。
以 \(K=4\) 为例:
矩阵:
也就是下三角矩阵。
逆矩阵:
高维 min 卷积
把上面的矩阵改成上三角就好了。
高维异或卷积
需要用点科技,先放放吧。
Examples
CF1119H Triple
solution
对于第 \(i\) 个数组,设集合幂级数 \(F_i(x)=X\times x^{a_i}+Y\times x^{b_i}+Z\times x^{c_i}\)。
那么\(Ans=\prod_{i=1}^n F_i\)。
其中乘法为异或卷积。
考虑把每个 \(F_i\) 先 \(FWT\) 然后点乘之后再 \(IFWT\)。
依次考虑每一位 \(i\),算出 \(\prod_{j=1}^n FWT(F_j)_i=\prod_{j=1}^n (-1)^{|i\& a_j|}+(-1)^{|i\& b_j|}+(-1)^{| i\& c_j|}\)
这样看似乎不可做,但是考虑后面只有 \(2^3\)中不同的情况,我们算出每种情况的个数就可以直接计算了。
但是 \(8\) 种还是有点多,考虑令 \(a_i,b_i,c_i\) 都异或 \(a_i\),最后再异或 \(\bigoplus_{i=1}^n a_i\)。
那么现在 \(|i\& a_j|=0\),贡献确定了,就只剩下 \(4\) 种情况了。
设 \(X+Y+Z,X+Y-Z,X-Y+Z,X-Y-Z\) 的个数分别是 \(A,B,C,D\),考虑找一些等量关系。
首先由:
- \(A+B+C+D=n\)
然后,我们考虑带入一些 \(X,Y,Z\)的特值。
- 只考虑\(x^{b_i}\)
那么 $$\sum_{i=1}^nFWT(F_i)=A+B-C-D$$
因为 \(FWT(F+G)=FWT(F)+FWT(G)\),所以 $$\sum_{i=1}^n FWT(F_i)=FWT(\sum_{i=1}^nF_i)$$
- 只考虑 \(x^{c_i}\)
那么 $$\sum_{i=1}^nFWT(F_i)=A-B+C-D$$
- 只考虑 \(x^{b_i\bigoplus c_i}\)
那么 $$\sum_{i=1}^nFWT(F_i)=A-B-C+D$$
这样就可以解出来 \(A,B,C,D\) 了。
Bonus
考虑拓展到 任意的 \(k\) 元组。
仿照上面的思路,我们枚举一个子集 \(S\in [1,2^k-1]\),计算只考虑 $$x^{\bigoplus_{u\in S} w_u}$$,那么 \(FWT(F_j)_i=\prod_{u\in S}(-1)^{|i\& w_{j,u}|}\)。
那么 \(\sum_j FWT(F_j)_i=\sum_{u=0}^{2^n-1}C_uf_{u,S}\),其中 \(c_u\) 是 \(u\) 这种情况的出现次数, \(f_{u,S}\) 是贡献系数。
容易发现 \(f_{u,S}=(-1)^{|u\& S|}\),因此我们对于每个\(i\),求出一个长度为\(2^k\)的数组\(H\),那么 \(H=FWT(c)\),因此只需要\(IFWT(H)\)就可以啦
CF582E Boolean Function
solution
注意到一共有 \(2^4\) 种不同的 \(A,B,C,D\)的取值,也就是说一共有 \(2^{16}\)种不同的结果,所以建出表达式树,那么每个非叶节点的\(dp\)为儿子的\(dp\)值的位运算卷积。
复杂度\(O(16n2^{16})\)
#include<bits/stdc++.h>
using namespace std;
const int N = 520;
const int mod = 1e9+7;
void FWTor(int *fwt,int n,int opt)
{
for(int len=2,l=1;len<=n;len<<=1,l<<=1)
for(int i=0;i<n;i+=len)
for(int j=i;j<i+l;j++)
fwt[j+l]=(fwt[j+l]+1ll*fwt[j]*opt%mod+mod)%mod;
}
void FWTand(int *fwt,int n,int opt)
{
for(int len=2,l=1;len<=n;len<<=1,l<<=1)
for(int i=0;i<n;i+=len)
for(int j=i;j<i+l;j++)
fwt[j]=(fwt[j]+1ll*fwt[j+l]*opt%mod+mod)%mod;
}
int dp[N][(1<<16)+7];
char s[N];
int get(char c)
{
int res=0,state=0;
for(int S=0;S<(1<<4);S++)
{
if('A'<=c&&c<='D')res=((S>>(c-'A'))&1);
if('a'<=c&&c<='d')res=1-((S>>(c-'a'))&1);
state=state|(res<<S);
}
return state;
}
int match[N],L[(1<<16)],R[1<<16];
void And(int *A,int *B,int *C)
{
for(int i=0;i<(1<<16);i++)L[i]=B[i],R[i]=C[i];
FWTand(L,(1<<16),1);FWTand(R,(1<<16),1);
for(int i=0;i<(1<<16);i++)L[i]=1ll*L[i]*R[i]%mod;
FWTand(L,(1<<16),-1);
for(int i=0;i<(1<<16);i++)A[i]=(A[i]+L[i])%mod;
}
void Or(int *A,int *B,int *C)
{
for(int i=0;i<(1<<16);i++)L[i]=B[i],R[i]=C[i];
FWTor(L,(1<<16),1);FWTor(R,(1<<16),1);
for(int i=0;i<(1<<16);i++)L[i]=1ll*L[i]*R[i]%mod;
FWTor(L,(1<<16),-1);
for(int i=0;i<(1<<16);i++)A[i]=(A[i]+L[i])%mod;
}
void solve(int l,int r)
{
if(l==r)
{
if(s[l]=='?')
{
for(int c='A';c<='D';c++)
{
int state=get(c);
dp[l][state]=(dp[l][state]+1)%mod;
}
for(int c='a';c<='d';c++)
{
int state=get(c);
dp[l][state]=(dp[l][state]+1)%mod;
}
}
else
{
int state=get(s[l]);
dp[l][state]=(dp[l][state]+1)%mod;
}
return;
}
solve(l+1,match[l]-1);
solve(match[r]+1,r-1);
int x=match[l]+1;
if(s[x]!='|')And(dp[l],dp[l+1],dp[match[r]+1]);
if(s[x]!='&') Or(dp[l],dp[l+1],dp[match[r]+1]);
}
int n,m;
int stk[N],top=0;
int A[N],B[N],C[N],D[N],E[N];
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;i++)
{
if(s[i]!='('&&s[i]!=')')continue;
if(s[i]=='(')stk[++top]=i;
else
{
int x=stk[top--];
match[x]=i;
match[i]=x;
}
}
cin>>m;
for(int i=1;i<=m;i++)
cin>>A[i]>>B[i]>>C[i]>>D[i]>>E[i];
solve(1,n);
int ans=0;
for(int S=0;S<(1<<16);S++)
if(dp[1][S])
{
bool flag=1;
for(int i=1;i<=m;i++)
{
int res=0;
res+=(A[i]<<0);
res+=(B[i]<<1);
res+=(C[i]<<2);
res+=(D[i]<<3);
if((S>>res)%2!=E[i])flag=0;
}
if(flag)ans=(ans+dp[1][S])%mod;
}
cout<<ans;
return 0;
}
CF908H New Year and Boolean Bridges
solution
对于用"A"相连的边,他们一定构成了强连通分量,我们先把他们连起来,对于 "X" 的边,他们必须不能再同一个强连通分量。
对于一个强连通分量,最少的边数显然是一个环。
现在我们就是要合并一些连通分量使得缩点之后剩下一条链。
对于两个连通分量,我们可以将他们变成一个强连通分量,或者连成一条链。
如果环的大小分别是\(A,B\),前者代价是 \(A+B\),后者是 \(A+B+1\),因此我们肯定要贪心的把两个小的合并成一个大的。
因为大小为 \(1\) 的连通分量不影响,所以设最终大小\(>1\)的强连通分量个数为 \(C\),代价为 \(n+C-1\)。
如果我们设把\(S\)这个点集联通的最小代价是 \(dp[S]\),那么转移就是\(min-\)子集卷积,显然不可做。
但是注意到代价小于等于 \(n\),因此不妨改成 \(dp[E][S]\)表示能否把\(S\)合并成\(E\)个连通分量,转移是子集卷积,但是注意到如果一个集合合法,他的子集都合法,因此可以直接用或卷积,最终第一次合法的\(E\)就是答案
// LUOGU_RID: 107709248
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 48;
const int M = (1<<23)+7;
char s[N][N];
int popcnt[M],lg[M];
int f(int x,int y){return ((x|y)==x)*((popcnt[x^y]&1)?-1:1);}
void FWT(int *fwt,int n)
{
for(int len=2,l=1;len<=n;len<<=1,l<<=1)
for(int i=0;i<n;i+=len)
for(int j=i;j<i+l;j++)
fwt[j+l]=fwt[j+l]+fwt[j];
}
int fa[N],siz[N];
int mask[N];
int n,tot;
int col[N];
int dem[M];
int calc[M],dp[M];
int Find(int x)
{
if(x==fa[x])return x;
return fa[x]=Find(fa[x]);
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=n;i++)
{
scanf("%s",s[i]+1);
for(int j=1;j<i;j++)
if(s[i][j]=='A')fa[Find(i)]=Find(j);
}
for(int i=1;i<=n;i++)siz[Find(i)]++;
for(int i=1;i<=n;i++)
if(i==Find(i))
{
if(siz[i]==1)continue;
col[i]=++tot;
}
if(tot==0)
{
printf("%d\n",n-1);
return 0;
}
for(int i=1;i<=n;i++)
for(int j=1;j<i;j++)
if(s[i][j]=='X')
{
int x=Find(i),y=Find(j);
if(x==y)
{
cout<<"-1";
return 0;
}
if(siz[x]==1||siz[y]==1)continue;
mask[col[x]]|=(1<<(col[y]-1));
mask[col[y]]|=(1<<(col[x]-1));
}
lg[0]=-1;
calc[0]=1;
for(int i=1;i<(1<<tot);i++)
{
lg[i]=lg[i>>1]+1;
popcnt[i]=popcnt[i>>1]+(i&1);
int t=lg[i&-i]+1;
if((mask[t]&(i-(i&-i)))==0)calc[i]=calc[i-(i&-i)];
}
for(int i=0;i<(1<<tot);i++)dem[i]=f((1<<tot)-1,i),dp[i]=1;
FWT(calc,(1<<tot));
for(int E=0;;E++)
{
for(int i=0;i<(1<<tot);i++)dp[i]=dp[i]*calc[i];
int res=0;
for(int i=0;i<(1<<tot);i++)res=res+dp[i]*dem[i];
if(res)
{
printf("%d\n",n+E);
return 0;
}
}
return 0;
}
CF838C Future Failure
solution
对于一个字符串,如果\(i\)出现了\(a_i\)次,那么不同的排列方式个数为\(C=\frac{n!}{\prod a_i!}\)
结论1:如果\(2|C\),先手必胜
证明:
若先手可以通过删字符达到一个必败态,显然先手必胜。
否则先手只能选择重排,重排之后如果对手选择删字符,根据上面,必败。
因此后手也会重排,但是因为\(C\)是偶数,最终必定是先手赢。
若 \(C\) 是奇数,那么先手必须选择删字符。
先手肯定尽量不想让下一步的局面变成 \(2|C\),因此必须保持奇数。
不妨删除一个\(2\)的次数最小的\(a_i\)然后-1,显然这个次数一定不超过\(n\)里面\(2\)的次数,因此删掉之后方案数乘上\(\frac{a}{n}\),显然\(2\)的个数不会增加,因此必定还是奇数。
这也就是说,谁先删完谁赢,即如果\(n\)是偶数就输,奇数就赢。
现在考虑计数。
如果\(n\)是奇数,显然任意的都合法,方案数\(k^{n}\)。
根据勒让德定理,\(\binom{a+b}{a}\) 中 \(p\) 的次数为\(p\)进制下\(a+b\)的进位次数,显然也可以拓展到多个数。
因此排列个数是奇数的充要条件是\(a_1 | a_2 |……a_k=a_1+a_2……a_k\)。
也就是子集卷积,然后就完了。
#include<bits/stdc++.h>
using namespace std;
const int N = (1<<18)+1;
int n,k,mod;
typedef long long LL;
const int B = 19;
inline int plu(int x,int y){return (x+y>=mod?x+y-mod:x+y);}
inline int dec(int x,int y){return (x-y<0?x-y+mod:x-y);}
int Pow(int a,int b)
{
int res=1;
while(b)
{
if(b&1)res=1ll*res*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return res;
}
int limit;
int D;
void FWT(int *f)
{
for(int len=2,l=1;len<=limit;len<<=1,l<<=1)
for(int i=0;i<limit;i+=len)
for(int j=i;j<i+l;j++)
f[j+l]=plu(f[j+l],f[j]);
}
void IFWT(int *f)
{
for(int len=2,l=1;len<=limit;len<<=1,l<<=1)
for(int i=0;i<limit;i+=len)
for(int j=i;j<i+l;j++)
f[j+l]=dec(f[j+l],f[j]);
}
int dp[B][(1<<B)],f[B][(1<<B)];
int popcnt[N];
int ifac[N],fac[N];
void put(int *f)
{
for(int i=0;i<limit;i++)
cout<<f[i]<<' ';
cout<<endl;
}
int main()
{
cin>>n>>k>>mod;
if(n%2==1)
{
cout<<Pow(k,n);
return 0;
}
ifac[1]=1;
fac[0]=1;
limit=(1<<18);
for(int i=1;i<limit;i++)
popcnt[i]=popcnt[i>>1]+(i&1);
int D=popcnt[n];
for(int i=2;i<limit;i++)
ifac[i]=1ll*ifac[mod%i]*(mod-mod/i)%mod;
ifac[0]=1;
for(int i=1;i<limit;i++)
ifac[i]=1ll*ifac[i-1]*ifac[i]%mod;
for(int i=1;i<limit;i++)
fac[i]=1ll*fac[i-1]*i%mod;
for(int i=0;i<limit;i++)
f[popcnt[i]][i]=ifac[i],dp[0][i]=1;
for(int i=0;i<=D;i++)
FWT(f[i]);
int u=k;
while(k)
{
if(k&1)
{
for(int i=D;i>=0;i--)
for(int j=0;j<i;j++)
for(int k=0;k<limit;k++)
dp[i][k]=plu(dp[i][k],1ll*dp[j][k]*f[i-j][k]%mod);
}
for(int i=D;i>=0;i--)
for(int j=0;j<i;j++)
for(int k=0;k<limit;k++)
f[i][k]=plu(f[i][k],1ll*f[j][k]*f[i-j][k]%mod);
k>>=1;
}
IFWT(dp[popcnt[n]]);
int ans=dec(Pow(u,n),1ll*dp[popcnt[n]][n]*fac[n]%mod);
cout<<ans;
return 0;
}
CF1326F2 Wise Men (Hard Version)
solution
因为同时要求有边和无边,比较难处理,所以不妨我们先不处理无边的限制,也就是说对于1,我们还是限制两点有边,对于0,我们不做限制。
可以发现这时候的0既可以当做0又可以当做1,因此现在求的答案是原答案的高维后缀和,差分回去就好了。
那么现在序列可以被划分成若干连续段,每段都是1。
对应到原图上,就是原图被划分成若干条链,我们把单点也算作链。
首先求出\(g_S\)表示选出一条覆盖的点集为\(S\)的链的方案数。
我们把它存到\(G_{|S|,S}\)中,就类似于子集卷积中的占位集合幂级数。
因为考虑到最终01段的划分是无序的,所以实际上的状态数是18的分拆数是很小的。
我们直接爆搜分拆数就好了。
对于方案数,我们只需要求出最终点集的并集为\(S\)的方案数\(f_S\)就好了。
为什么?乍一看我们这样做无法保证点集之间互不相交,但是事实上所有幂级数的大小之和就是\(n\),那我们既然覆盖的点集也是\(n\),自然就不交了。
因此我们只需要做或卷积就行了。
先对每一个\(G_i\)做FWTor,然后\(dfs\)的时候直接点值相乘。
最后我们应该要再做IFWTor,但是我们只需要求\(2^n-1\)项的值,所以根据IFWT的本质是高维差分,直接容斥计算就好了。
#include<bits/stdc++.h>
using namespace std;
const int N = 19;
const int M = (1<<18)+7;
typedef long long LL;
int n;
void FWTor(LL *fwt,int lim)
{
for(int len=2,l=1;len<=lim;len<<=1,l<<=1)
for(int i=0;i<lim;i+=len)
for(int j=i;j<i+l;j++)
fwt[j+l]+=fwt[j];
}
void IFWT(LL *fwt,int lim)
{
for(int len=2,l=1;len<=lim;len<<=1,l<<=1)
for(int i=0;i<lim;i+=len)
for(int j=i;j<i+l;j++)
fwt[j]-=fwt[j+l];
}
LL dp[N][M];
LL g[N][M];
char mp[N][N];
inline int bit(int x){return 1ll<<(x-1);}
inline int get(int x,int v){return (x>>(v-1))&1;}
int popcnt[M];
int U;
LL f[N][M];
map<vector<int> ,LL> DP;
vector<int> perm;
void GenerSet(int x,int a,int k)
{
if(x==n+1)
{
LL ans=0;
for(int S=0;S<=U;S++)
{
int c=popcnt[U^S];
if(c&1) ans-=f[k][S];
else ans+=f[k][S];
}
DP[perm]=ans;
return;
}
for(int i=a;i+x<=n+1;i++)
{
perm.push_back(i);
for(int S=0;S<=U;S++)
f[k+1][S]=g[i][S]*f[k][S];
GenerSet(x+i,i,k+1);
perm.pop_back();
}
}
LL ret[M];
int main()
{
cin>>n;
U=(1<<n)-1;
for(int i=1;i<=n;i++)
scanf("%s",mp[i]+1);
for(int i=1;i<=n;i++)
dp[i][bit(i)]=1;
for(int i=1;i<=U;i++)popcnt[i]=popcnt[i>>1]+(i&1);
for(int S=1;S<=U;S++)
for(int i=1;i<=n;i++)
if(dp[i][S])
{
for(int j=1;j<=n;j++)
if(get(S,j)==0&&mp[i][j]=='1')
dp[j][S|bit(j)]+=dp[i][S];
g[popcnt[S]][S]+=dp[i][S];
}
for(int i=1;i<=n;i++)FWTor(g[i],U+1);
for(int i=0;i<=U;i++)f[0][i]=1;
GenerSet(1,1,0);
for(int S=0;S<(1<<(n-1));S++)
{
perm.clear();
int pre=-1;
for(int i=0;i<n-1;i++)
if(((S>>i)&1)==0)
{
perm.push_back(i-pre);
pre=i;
}
perm.push_back(n-1-pre);
sort(perm.begin(),perm.end());
ret[S]=DP[perm];
}
IFWT(ret,(1<<(n-1)));
for(int i=0;i<(1<<(n-1));i++)
printf("%lld ",ret[i]);
return 0;
}

浙公网安备 33010602011771号