题目
正解
首先这题的
B
B
B矩阵可以看成邻接矩阵,于是
a
a
a的意义相当于选择若干条链,这些链之间首尾不相接。
按照套路,首先将链首尾不相接的限制去掉,只需要满足这些链内部是互相连接的。简单容斥一下,把“恰好”变成“至少”,计算完之后再反演回去。
可以观察到去掉这个限制之后,若干条链的排列顺序和方案数是无关的。于是状态数缩减为
n
n
n的划分数,
17
17
17的划分数为
297
297
297。
接下来枚举每种划分,并且计算它们的贡献。
设划分中第
i
i
i段长度为
p
i
p_i
pi,默认
p
i
p_i
pi从大到小排列。
设
f
(
S
)
f(S)
f(S)为选了集合
S
S
S的点连出一条链的方案数。
于是它的贡献为
∑
∣
S
i
∣
=
p
i
,
S
1..
n
之
间
无
交
集
∏
f
(
S
i
)
\sum_{|S_i|=p_i,S{1..n}之间无交集} \prod f(S_i)
∑∣Si∣=pi,S1..n之间无交集∏f(Si)
感觉这个东西不是很好求,要求
S
1..
n
S_{1..n}
S1..n之间无交集的条件好像不好搞。
但是可以发现,由于满足了
∣
S
i
∣
=
p
i
|S_i|=p_i
∣Si∣=pi,所以如果有交集,那么它们的并集就不是全集。
我们只需要求或卷积之后全集的方案数。
整理一下,设
F
k
(
S
)
=
∑
∣
S
∣
=
k
f
(
S
)
F_k(S)=\sum_{|S|=k}f(S)
Fk(S)=∑∣S∣=kf(S)
我们需要算
(
F
p
1
∗
F
p
2
∗
.
.
.
∗
F
p
k
)
(
U
)
(F_{p_1}*F_{p_2}*...*F_{p_k})(U)
(Fp1∗Fp2∗...∗Fpk)(U),这里的
∗
*
∗表示或卷积,
U
U
U表示全集。
先预处理出
f
(
S
)
f(S)
f(S),然后得到
F
k
(
S
)
F_k(S)
Fk(S),对所有的
F
k
F_k
Fk做
F
W
T
FWT
FWT。
接下来枚举划分,对于划分中的一段长度
p
i
p_i
pi,用当前的序列按位乘
F
p
i
F_{p_i}
Fpi。
搞完之后计算
U
U
U的方案数,由于我们只需要计算
U
U
U,所以没有必要
I
F
W
T
IFWT
IFWT,直接子集反演将
U
U
U单个位置的方案数算出来即可。
这一部分的时间复杂度
O
(
297
∗
2
n
∗
?
)
O(297*2^n*?)
O(297∗2n∗?),
?
?
?是某个玄学有关
n
n
n的函数(还是常数?),反正非常小……(产生自枚举划分的过程中,边枚举边按位乘)
接下来将划分还原成
a
a
a。
一种朴素的想法是直接枚举划分的全排列,但是发现这样是阶乘复杂度,显然不能过(而且这样做的时候,前面算出来的方案数还要除以一个东西。具体来说就是相同长度的个数的阶乘的乘积)。
比较正确的想法是将划分中长度相同的压在一起考虑,不用考虑它们之间的相对顺序。这是因为在前面计算的过程中已经会把相对顺序的贡献计算进去(就是上文说的除以的那个东西)。
这样时间复杂度是对的,因为每个不同的
a
a
a只会被枚举到一次。总时间复杂度约
O
(
2
n
−
1
)
O(2^{n-1})
O(2n−1)。
枚举到
a
a
a之后,直接将它的贡献加入一个数组的对应位置。
全部搞完之后,最后在做一遍与卷积的
I
F
W
T
IFWT
IFWT(注意这个和前面的或卷积
F
W
T
FWT
FWT没有关系),即可以将“至少”反演回“恰好”,得到真正的答案。
询问时
O
(
1
)
O(1)
O(1)的,因为前面已经求出了所有
a
a
a的答案。
代码
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;
}