Description

n
<
=
17
,
Q
<
=
1
e
5
n<=17,Q<=1e5
n<=17,Q<=1e5
Solution
- 考虑容斥,设一个长度为 n − 1 n-1 n−1的0/1串,0的位置 a i = 0 / 1 a_i=0/1 ai=0/1,1的位置 a i = 1 a_i=1 ai=1,这个状态对应的 a i a_i ai的排列的方案数。
- 那么对于一个 00100110110... 00100110110... 00100110110...,相邻的 1 1 1表示这条链一定里面全是1, 0 0 0则没有限制,那么这个串就把 n n n个点分成了若干个互补相关的链。
- 考虑这种分法一共有 n n n的整数划分 P ( n ) = 297 P(n)=297 P(n)=297种,可以暴力所有的分法,那么要求所有的链的并集为 n n n。
- 如果我们把每一种链可行的集合找出来,这个集合要选出 c [ i ] c[i] c[i]条链(不可重),最后拼出 n n n。这实际上相当于是一个子集卷积。
- 因为总数就是n,所以贡献到 2 n − 1 2^n-1 2n−1位置一定不会有重复的点,所以直接FWT即可。预先处理点值,递归的时候乘一乘,用简单的DP做链的状况。
- 求出每一种01串的方案数,就可以反演回去了。因为原先的形式相当于是And卷积的形式,所以IFWT回去就好(也可以理解为高维前缀和的逆运算)。
- 一道灵活运用FWT子集转移的题目
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define ll long long
#define maxn 17
using namespace std;
int n,q,i,j,k,a[maxn][maxn],cnt[1<<maxn],d[maxn+1][1<<maxn],c[maxn+1];
ll f[1<<maxn][maxn],g[maxn+1][1<<maxn];
void fwt(ll *a){
int N=1<<n;
for(int i=1;i<N;i<<=1)
for(int j=0;j<N;j+=i<<1)
for(int k=0;k<i;k++)
a[j+k+i]+=a[j+k];
}
void ifwt(ll *a){
int N=1<<n;
for(int i=1;i<N;i<<=1)
for(int j=0;j<N;j+=i<<1)
for(int k=0;k<i;k++)
a[j+k]-=a[j+k+i];
}
ll F[maxn][1<<maxn],G[1<<maxn];
void cover(int s,ll sum,int S){
if (s==n){
G[S]+=sum;return;
}
for(int i=1;i<=n;i++) if (c[i])
c[i]--,cover(s+i,sum,(S<<i)|((1<<i-1)-1)),c[i]++;
}
void dg(int i,int res){
if (res&&(res<i||i>n)) return;
if (i>n||res==0){
for(int j=i;j<=n;j++) c[j]=0;
ll sum=0;
for(int j=0;j<1<<n;j++)
sum+=F[i-1][j]*(((n-cnt[j])&1)?-1:1);
cover(0,sum,0);
return;
}
for(int j=0;i*j<=res;j++){
c[i]=j,memcpy(F[i],F[i-1],sizeof(F[i]));
dg(i+1,res-i*j);
for(int k=0;k<1<<n;k++) F[i-1][k]=F[i-1][k]*g[i][k];
}
}
int main(){
// freopen("s1mple.in","r",stdin);
// freopen("s1mple.out","w",stdout);
scanf("%d",&n); char ch=getchar();
for(i=0;i<n;i++){
while (ch!='0'&&ch!='1') ch=getchar();
for(j=0;j<n;j++) a[i][j]=ch-'0',ch=getchar();
}
for(i=0;i<n;i++) f[1<<i][i]=1;
for(int S=0;S<1<<n;S++) for(i=0;i<n;i++) if (f[S][i])
for(j=0;j<n;j++) if (!(S>>j&1)&&a[i][j])
f[S|(1<<j)][j]+=f[S][i];
for(i=1;i<1<<n;i++) {
cnt[i]=cnt[i>>1]+(i&1);
d[cnt[i]][++d[cnt[i]][0]]=i;
}
for(i=1;i<=n;i++) {
for(j=1;j<=d[i][0];j++) {
int S=d[i][j];
for(k=0;k<n;k++) if (S>>k&1)
g[i][S]+=f[S][k];
}
fwt(g[i]);
}
for(i=0;i<1<<n;i++) F[0][i]=1;
dg(1,n);
ifwt(G);
scanf("%d",&q),ch=getchar();
while (q--){
while (ch!='0'&&ch!='1') ch=getchar();
int P=0; for(i=1;i<n;i++) P=P<<1|(ch-'0'),ch=getchar();
printf("%lld\n",G[P]);
}
}

本文深入探讨了子集卷积和快速沃尔什变换(FWT)算法在解决特定组合数学问题中的应用。通过详细的代码示例,解释了如何使用FWT进行子集卷积,以及如何通过逆FWT(IFWT)回溯解决方案。文章重点介绍了子集卷积的概念,以及FWT在高效处理子集操作中的优势。

被折叠的 条评论
为什么被折叠?



