6724. 【2020.06.15省选模拟】T1 s1mple

题目


正解

首先这题的BB矩阵可以看成邻接矩阵,于是aa的意义相当于选择若干条链,这些链之间首尾不相接。
按照套路,首先将链首尾不相接的限制去掉,只需要满足这些链内部是互相连接的。简单容斥一下,把“恰好”变成“至少”,计算完之后再反演回去。
可以观察到去掉这个限制之后,若干条链的排列顺序和方案数是无关的。于是状态数缩减为nn的划分数,1717的划分数为297297

接下来枚举每种划分,并且计算它们的贡献。
设划分中第ii段长度为pip_i,默认pip_i从大到小排列。
f(S)f(S)为选了集合SS的点连出一条链的方案数。
于是它的贡献为Si=pi,S1..nf(Si)\sum_{|S_i|=p_i,S{1..n}之间无交集} \prod f(S_i)
感觉这个东西不是很好求,要求S1..nS_{1..n}之间无交集的条件好像不好搞。
但是可以发现,由于满足了Si=pi|S_i|=p_i,所以如果有交集,那么它们的并集就不是全集。
我们只需要求或卷积之后全集的方案数。

整理一下,设Fk(S)=S=kf(S)F_k(S)=\sum_{|S|=k}f(S)
我们需要算(Fp1Fp2...Fpk)(U)(F_{p_1}*F_{p_2}*...*F_{p_k})(U),这里的*表示或卷积,UU表示全集。
先预处理出f(S)f(S),然后得到Fk(S)F_k(S),对所有的FkF_kFWTFWT
接下来枚举划分,对于划分中的一段长度pip_i,用当前的序列按位乘FpiF_{p_i}
搞完之后计算UU的方案数,由于我们只需要计算UU,所以没有必要IFWTIFWT,直接子集反演将UU单个位置的方案数算出来即可。
这一部分的时间复杂度O(2972n?)O(297*2^n*?)??是某个玄学有关nn的函数(还是常数?),反正非常小……(产生自枚举划分的过程中,边枚举边按位乘)

接下来将划分还原成aa
一种朴素的想法是直接枚举划分的全排列,但是发现这样是阶乘复杂度,显然不能过(而且这样做的时候,前面算出来的方案数还要除以一个东西。具体来说就是相同长度的个数的阶乘的乘积)。
比较正确的想法是将划分中长度相同的压在一起考虑,不用考虑它们之间的相对顺序。这是因为在前面计算的过程中已经会把相对顺序的贡献计算进去(就是上文说的除以的那个东西)。
这样时间复杂度是对的,因为每个不同的aa只会被枚举到一次。总时间复杂度约O(2n1)O(2^{n-1})
枚举到aa之后,直接将它的贡献加入一个数组的对应位置。
全部搞完之后,最后在做一遍与卷积的IFWTIFWT(注意这个和前面的或卷积FWTFWT没有关系),即可以将“至少”反演回“恰好”,得到真正的答案。
询问时O(1)O(1)的,因为前面已经求出了所有aa的答案。


代码

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章