Codeforces 1326F Wise Men (容斥原理、状压 DP、子集和变换、划分数)

题目链接

F1: https://codeforces.com/contest/1326/problem/F1
F2: https://codeforces.com/contest/1326/problem/F2

题解

好题。
考虑容斥,对每个 01 串求满足串中为 (1) 的位置必须为 (1)、串中为 (0) 的位置 (0)(1) 均可的排列的个数。最后把超集和还原回来即可。
这样的好处是,本质不同的状态只有拆分数 (P(n)) 个,即一个状态的答案只和所有连续的 (1) 的长度构成的可重集合有关。于是考虑 DFS 枚举划分数。
先预处理 (f_S) 表示 (S) 点集内有多少条哈密尔顿回路经过的边全是 (1).
对于一个状态,假设每一段的长度是 (a_1,a_2,...,a_l). 那么就相当于我们要找 (l) 个状态 (s_1,s_2,...,s_l), 满足 (forall i, ext{bitcnt}(s_i)=a_i)( ext{or}^l_{i=1}s_i=2^n-1),贡献为 (prod^l_{i=1}f_{s_i}). 写成集合幂级数的形式,设 (g_i) 是一个集合幂级数,满足有且仅有在 ( ext{bitcnt}(s)=i) 的位置有值,值为 (f_s),则这个状态的总方案数等于 (g_{a_1},g_{a_2},...,g_{a_l}) 的子集卷积。于是可以直接使用子集卷积计算,可以做到 (O(2^nP(n)n^2)) 左右的复杂度。但是还是不行。
注意到 (sum^l_{i=1}a_i=n),且 (g_{a_i}) 仅仅在 ( ext{bitcnt}(a_i)) 处有值。也就是说我们其实根本不需要使用子集卷积——子集卷积的方法是给每个集合幂级数增加一维长度的限制,但是这里我们已经对长度进行了限制!假设有任何两个 (s_i) 有交,那么所有 (s_i) 的并的大小就不可能为 (n). 于是直接对 (g_{a_i}) 这些集合幂级数作 or 卷积即可。
枚举划分后,计算 or 卷积的时间复杂度为所有划分方案的总长度,足以通过。但是我们可以边 DFS 边维护 or 卷积,复杂度变成了搜索树的节点个数乘以 (2^n).
划分数搜索时,比较好的方法是从大到小搜索,每次放的数不超过上次放的。这时只要上次放的数不小于 (2),每个节点分叉数就一定大于 (1). 假设剩下一堆 (1) 是一起放的,那么节点数显然为 (O(P(n)));否则据 EI 爷说是 (O(P(n)sqrt n)) 的。这里可以预处理 (g_1) 的幂来做到前者的复杂度。
总时间复杂度 (O(2^n(n^2+T(n))),其中 (T(n)=O(P(n)))(O(P(n)sqrt n))(O( ext{sum of length of all partitions})).

代码

#include<bits/stdc++.h>
#define llong long long
#define mkpr make_pair
#define x first
#define y second
#define iter iterator
#define riter reversed_iterator
#define y1 Lorem_ipsum_dolor
using namespace std;

inline int read()
{
	int x = 0,f = 1; char ch = getchar();
	for(;!isdigit(ch);ch=getchar()) {if(ch=='-') f = -1;}
	for(; isdigit(ch);ch=getchar()) {x = x*10+ch-48;}
	return x*f;
}

const int mxN = 18;
int bitcnt[(1<<mxN)+3];
llong f[mxN+3][(1<<mxN)+3];
llong dp[(1<<mxN)+3][mxN+3];
llong g[mxN+3][(1<<mxN)+3];
int part[mxN+3],aux[mxN+3];
llong h[(1<<mxN)+3];
char a[mxN+3][mxN+3];
int n,m;

void dfs(int rst,int lst)
{
	if(rst==0)
	{
		llong ret = 0ll;
		for(int i=0; i<(1<<n); i++)
		{
			ret += ((n-bitcnt[i])&1)?-g[m][i]:g[m][i];
		}
//		printf("("); for(int i=1; i<=m; i++) printf("%d ",part[i]); printf("): %I64d
",ret);
		for(int i=1; i<=m; i++) {aux[i] = part[m-i+1];}
		do
		{
			int pos = 0,sta = 0;
			for(int i=1; i<=m; i++)
			{
				for(int j=1; j<aux[i]; j++,pos++) {sta|=(1<<pos);}
				pos++;
			}
//			printf("sta=%d
",sta);
			h[sta] += ret;
		} while(next_permutation(aux+1,aux+m+1));
		return;
	}
	for(int i=1; i<=rst&&i<=lst; i++)
	{
		part[++m] = i;
		for(int j=0; j<(1<<n); j++) {g[m][j] = g[m-1][j]*f[i][j];}
		dfs(rst-i,i);
		m--;
	}
}

int main()
{
	for(int i=1; i<(1<<mxN); i++) bitcnt[i] = bitcnt[i>>1]+(i&1);
	n = read(); for(int i=0; i<n; i++) {scanf("%s",a[i]); for(int j=0; j<n; j++) a[i][j] -= 48;}
	for(int i=0; i<n; i++) dp[1<<i][i] = 1ll;
	for(int i=1; i<(1<<n); i++) for(int j=0; j<n; j++) if(i&(1<<j))
	{
		llong x = dp[i][j];
		for(int k=0; k<n; k++) if(a[j][k]&&!(i&(1<<k)))
		{
			dp[i|(1<<k)][k] += x;
		}
	}
	for(int i=0; i<(1<<n); i++) for(int j=0; j<n; j++) if(i&(1<<j))
	{
		f[bitcnt[i]][i] += dp[i][j];
	}
//	for(int i=0; i<(1<<n); i++) printf("%I64d ",f[bitcnt[i]][i]); puts("");
	for(int i=0; i<n; i++) for(int j=0; j<(1<<n); j++) if(j&(1<<i))
	{
		for(int k=0; k<n; k++) {f[k][j] += f[k][j^(1<<i)];}
	}
	for(int i=0; i<(1<<n); i++) g[0][i] = 1ll; dfs(n,n);
	for(int i=0; i<n-1; i++) for(int j=0; j<(1<<n-1); j++) if(j&(1<<i))
	{
		h[j^(1<<i)] -= h[j];
	}
	for(int i=0; i<(1<<n-1); i++) printf("%I64d ",h[i]); puts("");
	return 0;
}
原文地址:https://www.cnblogs.com/suncongbo/p/12792845.html