Codeforces 1326F2 - Wise Men (Hard Version)(FWT+整数划分)

Codeforces 题目传送门 & 洛谷题目传送门

首先暴力 \(3^nn\) 枚举子集显然是过不去的,否则这题就退化到 F1 了(大雾

考虑用一个容斥的思想,考虑求出答案的后缀和 \(f_i\),其中 \(i\) 是一个长度为 \(n-1\) 的二进制串。形式化地说,\(f_i\) 等于满足以下条件的排列 \(p\) 的个数:如果 \(i\) 的第 \(j\) 位为 \(1\) 那么 \(p_j,p_{j+1}\) 必须认识,否则 \(p_j,p_{j+1}\) 可以认识可以不认识。显然求出 \(f_i\) 之后再做一遍高维差分——或者你叫它 IFWTand 我也没意见,即可求出真正的 \(ans_i\)

接下来考虑怎样求出 \(f_i\),首先我们将 \(i\) 用二进制表示,显然 \(i\) 的二进制表示可以划分成若干个连续的 \(1\) 组成的段,假设每一段的长度分别为 \(l_1,l_2,\cdots,l_m\),那么这个划分方案数就等价于将全集 \(U=\{1,2,\cdots,n\}\) 划分为 \(m\) 个集合 \(S_1,S_2,\cdots,S_m\),满足 \(|S_i|=l_i\),再将 \(S_i\) 中的所有人排成一列,满足相邻的人必须互相认识的方案数。注意到题目中涉及”对于集合 \(S\),将 \(S\) 中的人排成一列,满足相邻的人必须互相认识的方案数 \(g_S\)“的子问题,这个是非常容易求得的,简单设个 \(dp_{S,i}\) 表示将 \(S\) 中的人排成一列,最后一个人为 \(i\) 并且相邻的人互相认识的方案数,这样即可在 \(2^nn^2\) 的时间内求出 \(g_S\),这样一来 \(f_i=\sum\limits_{|S_i|=l_i,S_1\cup S_2\cdots\cup S_m=U}\prod\limits_{i=1}^mg_{S_i}\),这个看起来有点像子集卷积,我们再将原来的一维数组 \(g_S\) 扩充成二维数组 \(h_{i,S}\)\(h_{i,S}=\begin{cases}g_S&(|S|=i)\\0&(|S|\ne i)\end{cases}\),再将所有 \(h_{l_i}\) FWTor 卷起来得到集合幂级数 \(F\),最终的 \(F_U\) 即为 \(f_i\)

有人可能会说,你对每个 \(f_i\) 都搞一遍 FWTor,这样不就变成 \(4^nn\) 了吗?甚至跑不过 \(3^nn\) 的暴力。事实上注意到段与段之间的顺序是不重要的,而 \(18\) 的整数划分总共只有 \(385\) 种,因此我们可以对每个整数划分都求一遍答案,求 \(f_i\) 时直接调用刚刚求得的值即可。

时间复杂度大约是 \(\mathcal O(385·2^n+2^nn^2)\)

const int MAXP=1<<18;
const int MAXN=18;
int n;ll dp[MAXP+5][MAXN+5],g[MAXN+5][MAXP+5];
char s[MAXN+5][MAXN+5];
void FWTor(ll *a,int len,int type){
	for(int i=2;i<=len;i<<=1)
		for(int j=0;j<len;j+=i)
			for(int k=0;k<(i>>1);k++)
				a[(i>>1)+j+k]+=a[j+k]*type;
}
void FWTand(ll *a,int len,int type){
	for(int i=2;i<=len;i<<=1)
		for(int j=0;j<len;j+=i)
			for(int k=0;k<(i>>1);k++)
				a[j+k]+=a[(i>>1)+j+k]*type;
}
map<vector<int>,ll> ret;
vector<int> cur;
ll mul[MAXN+5][MAXP+5],ans[MAXP+5];
void dfs(int x,int lst){
	if(x==n+1){
		ll sum=0;
		for(int i=0;i<(1<<n);i++){
			int dig=bitcnt(((1<<n)-1)^i)&1;
			if(dig) sum-=mul[cur.size()][i];
			else sum+=mul[cur.size()][i];
		} ret[cur]=sum;
//		for(int x:cur) printf("%d ",x);printf("%lld\n",sum);
		return;
	}
	for(int i=lst;i<=n-x+1;i++){
		cur.pb(i);
		for(int j=0;j<(1<<n);j++) mul[cur.size()][j]=mul[cur.size()-1][j]*g[i][j];
		dfs(x+i,i);cur.pop_back();
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
	for(int i=1;i<=n;i++) dp[1<<i-1][i]=1;
	for(int i=1;i<(1<<n);i++) for(int j=1;j<=n;j++) if(i>>(j-1)&1){
		for(int k=1;k<=n;k++) if((i>>(k-1)&1)&&j!=k&&s[k][j]=='1'){
			dp[i][j]+=dp[i^(1<<j-1)][k];
		} g[bitcnt(i)][i]+=dp[i][j];
	}
//	for(int i=1;i<(1<<n);i++) printf("%lld\n",g[bitcnt(i)][i]);
	for(int i=1;i<=n;i++) FWTor(g[i],1<<n,1);
	for(int i=0;i<(1<<n);i++) mul[0][i]=1;dfs(1,1);
	for(int i=0;i<(1<<n-1);i++){
		vector<int> v;int pre=-1;
		for(int j=0;j<n-1;j++) if(~i>>j&1){
			v.pb(j-pre);pre=j;
		} v.pb(n-1-pre);sort(v.begin(),v.end());
//		for(int x:v) printf("%d ",x);printf("\n");
		ans[i]=ret[v];
	} FWTand(ans,1<<n-1,-1);
	for(int i=0;i<(1<<n-1);i++) printf("%lld ",ans[i]);
	return 0;
}
posted @ 2021-04-29 11:25  tzc_wk  阅读(55)  评论(0编辑  收藏  举报