题意
给定 n × n n\times n n×n 的 01 矩阵 M M M, q q q 次询问:给定长度 n − 1 n-1 n−1 的 01 序列 a a a,求有多少 1 到 n n n 的排列 p p p 使得 ∀ i ∈ [ 1 , n − 1 ] M p i , p i + 1 = a i \forall i\in[1,n-1]M_{p_i,p_{i+1}}=a_i ∀i∈[1,n−1]Mpi,pi+1=ai。 n ≤ 17 n\leq 17 n≤17, q ≤ 1 0 5 q\leq10^5 q≤105。
题解
先将 M M M 看作一个有向图,1 代表有边。
将 a a a 压缩为 S S S。记序列 S S S 的答案为 A [ S ] A[S] A[S], A A A 可以看成集合幂级数的形式。既有 1(必须有边)也有 0(必须无边)的限制显然不够好看,于是记 B [ S ] B[S] B[S] 代表 p p p 符合 S S S 中 1 的限制,不一定符合 S S S 中 0 的限制的排列数。即 B [ T ] = ∑ T ⊆ S A [ S ] B[T]=\sum\limits_{T\subseteq S}A[S] B[T]=T⊆S∑A[S]。那么 B ( S ) B(S) B(S) 可以看作几个路径连起来, S S S 给出了每条路径依次该有多长的信息。求出 B B B 之后可以反演得到 A A A。
记 g ( S ) g(S) g(S) 为集合 S S S 中的点可以形成的链的数量,显然它可以 O ( 2 n n 2 ) O(2^nn^2) O(2nn2) 预处理。记 f k [ S ] ( ∣ S ∣ = k ) = g ( S ) f_k[S](|S|=k)=g(S) fk[S](∣S∣=k)=g(S),将 f k f_k fk 看成集合幂级数的形式。
假如 S S S 对应的每条路径依次长 t i ( ∑ i = 1 l t i = n ) t_i(\sum\limits_{i=1}^{l}t_i=n) ti(i=1∑lti=n),那么 B [ S ] B[S] B[S] 为 ∏ i = 1 l f t i \prod\limits_{i=1}^lf_{t_i} i=1∏lfti 中全集对应的系数(乘法为集合并卷积)。即,把对应长度的路径选出来,并且他们并集为所有顶点(没有重合)。
对于对应的每条路径长度相同的 S S S, B [ S ] B[S] B[S] 也相同,所以只需要计算 A000041 ( n ) \text{A000041}(n) A000041(n) 次 B [ S ] B[S] B[S]( A000041 ( 17 ) = 297 \text{A000041}(17)=297 A000041(17)=297),每次的时间复杂度为 O ( 2 n n ) O(2^nn) O(2nn),只需要 O ( A000041 ( n ) 2 n n ) O(\text{A000041}(n)2^nn) O(A000041(n)2nn) 的复杂度便能计算出 B B B,然后计算出 A A A,然后 O ( 1 ) O(1) O(1) 询问。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int getint(){
int ans=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
ans=ans*10+c-'0';
c=getchar();
}
return ans*f;
}
int popc(int x){
return ( x &1)+((x>>1 )&1)+((x>>2 )&1)+((x>>3 )&1)+
((x>>4 )&1)+((x>>5 )&1)+((x>>6 )&1)+((x>>7 )&1)+
((x>>8 )&1)+((x>>9 )&1)+((x>>10)&1)+((x>>11)&1)+
((x>>12)&1)+((x>>13)&1)+((x>>14)&1)+((x>>15)&1)+
((x>>16)&1);
}
const int N=18;
bool con[N][N];
ll f[N][1<<N];//f[i] 为大小为 i 的集合能组成链的数量的集合幂级数
ll g[1<<N][N];//g[i][j] 为已经经过 i,现在在 j 的链数
ll a[1<<N];
ll ans[1<<N];
int tmp[N],l;
int _(int x,int n){
int t=l=0;
for(int i=n-2;i>=0;--i){
++t;
if(((x>>i)&1)==0){
tmp[l++]=t;
t=0;
}
}
tmp[l++]=t+1;
sort(tmp,tmp+l);
t=0;int ans=0;
for(int i=0;i<l;i++){
ans=(ans<<tmp[i])+((1<<tmp[i]-1)-1);
}
return ans;
}
void fwt(ll *a,int n){
for(int i=1;i<n;i<<=1)
for(int j=0;j<n;j+=i<<1)
for(int k=j;k<j+i;k++)
a[k+i]+=a[k];
}
void ifwt(ll *a,int n){
for(int i=1;i<n;i<<=1)
for(int j=0;j<n;j+=i<<1)
for(int k=j;k<j+i;k++)
a[k+i]-=a[k];
}
void qaq(ll *a,int n){
for(int i=1;i<n;i<<=1)
for(int j=0;j<n;j+=i<<1)
for(int k=j;k<j+i;k++)
a[k]-=a[k+i];
}
int main(){
int n=getint();
for(int i=0;i<n;i++)
for(int j=0;j<n;j++){
char c=getchar();
while(c!='0'&&c!='1')c=getchar();
con[i][j]=c-'0';
}
f[0][0]=1;
for(int i=0;i<(1<<n);i++){
int h=popc(i);
for(int j=0;j<n;j++){
if((i>>j)&1){
int t=i^(1<<j);
if(!t)g[i][j]=1;
for(int k=0;k<n;k++)
if(con[j][k])
g[i][j]+=g[t][k];
f[h][i]+=g[i][j];
}
}
//cerr<<"> "<<h<<" "<<i<<" "<<f[h][i]<<endl;
}
for(int i=0;i<=n;i++){
fwt(f[i],1<<n);
//for(int j=0;j<1<<n;j++)cerr<<" "<<f[i][j];cerr<<endl;
}
memset(ans,-1,sizeof(ans));
for(int i_=0;i_<(1<<n-1);i_++){
int i=_(i_,n);
//cerr<<"i "<<i_<<" -> "<<i<<endl;
if(ans[i]!=-1){
ans[i_]=ans[i];
continue;
}
for(int j=0;j<(1<<n);j++)a[j]=1;
for(int j=0;j<l;j++){
for(int k=0;k<(1<<n);k++){
a[k]*=f[tmp[j]][k];
}
}
//for(int j=0;j<(1<<n);j++)cerr<<" "<<a[j];cerr<<endl;
ifwt(a,1<<n);
//for(int j=0;j<(1<<n);j++)cerr<<" "<<a[j];cerr<<endl;
ans[i_]=ans[i]=a[(1<<n)-1];
}
//for(int i=0;i<1<<n-1;i++)cerr<<"| "<<ans[i];cerr<<endl;
qaq(ans,1<<n-1);
int q=getint();
while(q--){
int x=0;
for(int i=1;i<n;i++){
char c=getchar();
while(c!='0'&&c!='1')c=getchar();
x=(x<<1)|(c-'0');
}
//x=_(x,n);
//cerr<<"> "<<x<<" "<<ans[x]<<endl;
printf("%lld\n",ans[x]);
}
return 0;
}