題目
正解
首先這題的矩陣可以看成鄰接矩陣,於是的意義相當於選擇若干條鏈,這些鏈之間首尾不相接。
按照套路,首先將鏈首尾不相接的限制去掉,只需要滿足這些鏈內部是互相連接的。簡單容斥一下,把“恰好”變成“至少”,計算完之後再反演回去。
可以觀察到去掉這個限制之後,若干條鏈的排列順序和方案數是無關的。於是狀態數縮減爲的劃分數,的劃分數爲。
接下來枚舉每種劃分,並且計算它們的貢獻。
設劃分中第段長度爲,默認從大到小排列。
設爲選了集合的點連出一條鏈的方案數。
於是它的貢獻爲
感覺這個東西不是很好求,要求之間無交集的條件好像不好搞。
但是可以發現,由於滿足了,所以如果有交集,那麼它們的並集就不是全集。
我們只需要求或卷積之後全集的方案數。
整理一下,設
我們需要算,這裏的表示或卷積,表示全集。
先預處理出,然後得到,對所有的做。
接下來枚舉劃分,對於劃分中的一段長度,用當前的序列按位乘。
搞完之後計算的方案數,由於我們只需要計算,所以沒有必要,直接子集反演將單個位置的方案數算出來即可。
這一部分的時間複雜度,是某個玄學有關的函數(還是常數?),反正非常小……(產生自枚舉劃分的過程中,邊枚舉邊按位乘)
接下來將劃分還原成。
一種樸素的想法是直接枚舉劃分的全排列,但是發現這樣是階乘複雜度,顯然不能過(而且這樣做的時候,前面算出來的方案數還要除以一個東西。具體來說就是相同長度的個數的階乘的乘積)。
比較正確的想法是將劃分中長度相同的壓在一起考慮,不用考慮它們之間的相對順序。這是因爲在前面計算的過程中已經會把相對順序的貢獻計算進去(就是上文說的除以的那個東西)。
這樣時間複雜度是對的,因爲每個不同的只會被枚舉到一次。總時間複雜度約。
枚舉到之後,直接將它的貢獻加入一個數組的對應位置。
全部搞完之後,最後在做一遍與卷積的(注意這個和前面的或卷積沒有關係),即可以將“至少”反演回“恰好”,得到真正的答案。
詢問時的,因爲前面已經求出了所有的答案。
代碼
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 17
#define ll long long
int n;
char str[N+3];
int b[N][N];
ll dp[1<<N][N];
int cnt[1<<N];
ll F[N+1][1<<N];
void fwt_or(ll f[]){
for (int i=1;i<1<<n;i<<=1)
for (int j=0;j<1<<n;j+=i<<1)
for (int k=j;k<j+i;++k)
f[k+i]+=f[k];
}
void ifwt_and(ll f[]){
for (int i=1;i<1<<n-1;i<<=1)
for (int j=0;j<1<<n-1;j+=i<<1)
for (int k=j;k<j+i;++k)
f[k]-=f[k+i];
}
ll G[N+1][1<<N];
ll H[1<<N];
int p[N],q[N];
bool used[N];
void dfs2(int k,int n,int m,int s,int t,ll v){
if (k==n){
H[s]+=v;
return;
}
for (int i=0;i<m;++i)
if (q[i]){
q[i]--;
dfs2(k+1,n,m,s|(1<<p[i]-1)-1<<t,t+p[i],v);
q[i]++;
}
}
void dfs(int k,int m,int s,int x){
if (s==0){
ll sum=0;
for (int i=0;i<1<<n;++i)
sum+=(n-cnt[i]&1?-1:1)*G[k][i];
dfs2(0,k,m,0,0,sum);
return;
}
for (;x>=1;--x){
for (int i=0;i<1<<n;++i)
G[k+1][i]=G[k][i]*F[x][i];
if (m && p[m-1]==x){
q[m-1]++;
dfs(k+1,m,s-x,min(x,s-x));
q[m-1]--;
}
else{
p[m]=x,q[m]=1;
dfs(k+1,m+1,s-x,min(x,s-x));
}
}
}
int main(){
freopen("s1mple.in","r",stdin);
freopen("s1mple.out","w",stdout);
scanf("%d",&n);
for (int i=0;i<n;++i){
scanf("%s",str);
for (int j=0;j<n;++j)
b[i][j]=str[j]-'0';
}
for (int i=0;i<n;++i)
dp[1<<i][i]=1;
for (int i=1;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) && b[j][k])
dp[i|1<<k][k]+=dp[i][j];
cnt[0]=0;
F[0][0]=1;
for (int i=1;i<1<<n;++i){
cnt[i]=cnt[i>>1]+(i&1);
ll sum=0;
for (int j=0;j<n;++j)
if (i>>j&1)
sum+=dp[i][j];
F[cnt[i]][i]+=sum;
}
for (int i=0;i<=n;++i)
fwt_or(F[i]);
for (int i=0;i<1<<n;++i)
G[0][i]=1;
dfs(0,0,n,n);
ifwt_and(H);
int Q;
scanf("%d",&Q);
while (Q--){
scanf("%s",str);
ll s=0;
for (int i=0;i<n-1;++i)
s|=str[i]-'0'<<i;
printf("%lld\n",H[s]);
}
return 0;
}