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]);
}
}