【Codeforces 1326F2 Wise Men (Hard Version)】【狀壓dp+容斥原理】

題意

nn個點,有些點之間有邊。對於11nn的每個排列PP,構造長度爲n1n-10101strstr,其中str[i]=1str[i]=1當且僅當pip_ipi+1p_{i+1}之間有邊。對每個長度爲n1n-1的串strstr,求有多少個排列構造出來的串等於strstr
n18n\le 18

分析

考慮對每個串求它超集的答案之和,然後dp一次得到答案。

如果我們把有邊相連且在排列中相鄰的點連在一起,把序列分成了若干段,滿足每一段的長度之和爲nn。可以發現分段排序後相同的串它們的答案是一樣的,故只需要對nn的每種整數劃分方案,求出該方案的答案即可。

先預處理出gi,sg_{i,s}表示選出一條由ii個人組成的路徑的方案,其中ss是一個二進制狀態,表示選了哪ii個人。那麼nn的某種劃分的答案就是gai,si\sum\prod g_{a_i,s_i},其中ai=n\sum a_i=nsis_i的並集爲全集。

但這樣還是不好求。考慮求出dsd_s表示sis_i的並集爲ss的子集的貢獻,然後通過容斥來求總的貢獻。對gg求子集和,就有ds=gai,sd_s=\prod g_{a_i,s},就做完了。

寫法上還有一點優化,就是在dfs處理nn的整數劃分的時候,可以邊搜邊處理dsd_s,複雜度就變成了O((n2+P(n))2n)O((n^2+P(n))2^n),其中P(n)P(n)表示nn的整數劃分數目。

代碼

#include<bits/stdc++.h>
#define pb push_back

typedef long long LL;

const int N=23;
const int M=300005;

int n,bin[N],a[N][N],cnt[M];
LL dp[N][M],f[M],g[N][M],d[N][M];
std::map<int,LL> ma;

void pre()
{
	for (int i=1;i<=n;i++) dp[i][bin[i-1]]=1;
	for (int i=1;i<bin[n];i++)
		for (int j=1;j<=n;j++)
			if (dp[j][i])
			{
				g[cnt[i]][i]+=dp[j][i];
				for (int k=1;k<=n;k++)
					if (!(i&bin[k-1])&&a[j][k]) dp[k][i+bin[k-1]]+=dp[j][i];
			}
	for (int i=1;i<=n;i++)
		for (int j=1;j<=n;j++)
			for (int k=0;k<bin[n];k++)
				if (k&bin[j-1]) g[i][k]+=g[i][k^bin[j-1]];
}

void calc(int dep,LL sta)
{
	LL w=0;
	for (int i=0;i<bin[n];i++)
		if ((n-cnt[i])&1) w-=d[dep][i];
		else w+=d[dep][i];
	ma[sta]=w;
}

void dfs(int mx,int sum,int used,int dep,LL sta)
{
	if (sum==n) {calc(dep,sta);return;}
	if (sum+mx>n) return;
	for (int i=0;i<bin[n];i++) d[dep+1][i]=d[dep][i]*g[mx][i];
	dfs(mx,sum+mx,used+1,dep+1,sta*10+mx);
	dfs(mx+1,sum,0,dep,sta);
}

LL get_sta(int x)
{
	std::vector<int> vec;
	int s=0;
	for (int i=0;i<n;i++)
		if (x&bin[i]) s++;
		else vec.pb(s+1),s=0;
	std::sort(vec.begin(),vec.end());
	LL sta=0;
	for (int i=0;i<vec.size();i++) sta=sta*10+vec[i];
	return sta;
}

void solve()
{
	for (int i=0;i<bin[n-1];i++) f[i]=ma[get_sta(i)];
	for (int i=1;i<n;i++)
		for (int j=0;j<bin[n-1];j++)
			if (!(j&bin[i-1])) f[j]-=f[j^bin[i-1]];
	for (int i=0;i<bin[n-1];i++) printf("%lld ",f[i]);
}

int main()
{
	scanf("%d",&n);
	bin[0]=1;
	for (int i=1;i<=n;i++) bin[i]=bin[i-1]*2;
	for (int i=1;i<bin[n];i++) cnt[i]=cnt[i^(i&(-i))]+1;
	for (int i=1;i<=n;i++)
	{
		char str[N];
		scanf("%s",str+1);
		for (int j=1;j<=n;j++) a[i][j]=str[j]-'0';
	}
	pre();
	for (int i=0;i<bin[n];i++) d[0][i]=1;
	dfs(1,0,0,0,0);
	solve();
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章