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