[CF1326F] Wise Men

这本来是一道容斥好题,但是被 * 一样的子任务分配毁了……

(其实是因为一万个人过了 F1 但是我没过导致上分失败(x

倒着分析一下,设 \(f_s\) 表示状态 \(s\) 下的方案数,其中 \(s_i=1\) 表示第 \(i\) 个人与第 \(i+1\) 个人之间一定有边,\(s_i=0\) 表示他们之间可以有边,也可以没有边(注意 \(|s|=n-1\))。

那么通过 \(f_s\),我们可以容斥得到真实的答案。具体地说,只需要将高维前缀和变为高维后缀差分即可,即枚举 \(k\),每次枚举时 \(f_x := f_x-f_{x+2^k}\),当且仅当 \(x \operatorname{and} 2^k=0\)。正确性可以通过数学归纳法证明:\(|s|=1\) 时显然正确;\(|s|=m\) 时,设 \(|s|=m-1\) 时正确,则枚举 \(k\in[0, m-2]\) 后,每个 \(x\operatorname{and} 2^{m-1}\ne 0\)\(f_x\) 已经变成真实值, 且剩余的 \(f_x\) 中不含 \(2^{m-1}\) 的部分已经容斥完毕。发现剩余的 \(f_x\) 中,包含 \(2^{m-1}\) 的部分恰好都被 \(f_{x+2^{m-1}}\) 算到了,并且每一项的符号都恰与 \(f_{x+2^{m-1}}\) 对应的项相反,因此直接减去 \(f_{x+2^{m-1}}\) 就是对的。

现在的问题是怎么求 \(f_s\)。对于一个 \(s\) 中极长的一段连续的 \(1\),设其长度为 \(m\),则它代表一条包含了 \(m+1\) 个人的简单路径,其中相邻的人之间都有边。由于我们不关心连续段之间是否有边,每一个连续段都是独立的,它们的顺序也无关紧要。因此只需要对每一个 \(\sum a_i=n\) 的可重集合 \(a\),求出它对应的答案即可。搜一下发现这样的 \(a\) 最多只有 \(p_n=385\) 个。

现在我们的任务是,给定一个 \(\sum a_i =n\) 的可重集合,将所有人分成 \(|a|\) 组,满足第 \(i\) 组人数为 \(a_i\),且一种分配方法的权值为 \(\prod b_i\),其中 \(b_i\) 表示第 \(i\) 组的人的哈密顿路径数量。

考虑预处理 \(g_{k, s}\) 表示将 \(s_i=1\) 的人分配到同一组中,且组里恰好有 \(k\) 个人(即 \(s\) 中恰好有 \(k\) 个位置是 \(1\),否则 \(g_{k, s}=0\))的哈密顿路径数量,可以通过简单的状压 dp 完成。

如果对于每一个 \(a_i\),都暴力枚举不为 \(0\)\(g_{a_i, s}\) 进行状压 dp,复杂度大约是 \(\binom{\frac{n}{2}}{n}^2\) 级别,无法接受。

但是注意到我们要求的是给每个人分配恰好一个组的方案数。由于 \(\sum a_i=n\),所以如果某人被分配了多于一个组,则必然有人没有被分配到组。因此它的方案数等价于给每个人分配至少一个组的方案数。设 \(h_{k, s}\) 表示只在 \(s_i=1\) 的人中找到一条长度为 \(k\) 的哈密顿路径的方案数,显然 \(h_{k, s}=\sum_{t\subseteq s} g_{k, t}\)。设 \(d_s=\prod h_{a_i, s}\),则只需要容斥就可以得到 \(a\) 对应的答案。此处容斥类似 [ZJOI2016] 小星星。

如果暴力枚举 \(a\),再暴力枚举 \(a_i\) 计算,复杂度为 \(2^n\sum |a|\),但是如果在搜索 \(a\) 的过程中,即时算出每层对应的 \(d\),即可优化到 \(2^n p_n\),因为搜索树上分叉的数量等于叶节点数量。前面求 \(h_{k, s}\) 时需要对每个 \(k\) 做一遍高维前缀和(子集和),因此复杂度是 \(O\left(2^n(p_n+n^2)\right)\)

Code:

#include <bits/stdc++.h>
#define R register
#define mp make_pair
#define ll long long
#define pii pair<int, int>
using namespace std;
const int mod = 998244353, N = (1 << 18) + 100;

int n, g[20][20];
ll f[N][20], h[20][N], tmp[20][N], ans[N];
char s[20];
vector<int> a;

inline int addMod(int a, int b) {
	return (a += b) >= mod ? a - mod : a;
}

inline ll quickpow(ll base, ll pw) {
	ll ret = 1;
	while (pw) {
		if (pw & 1) ret = ret * base % mod;
		base = base * base % mod, pw >>= 1;
	}
	return ret;
}

template <class T>
inline void read(T &x) {
	x = 0;
	char ch = getchar(), w = 0;
	while (!isdigit(ch)) w = (ch == '-'), ch = getchar();
	while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
	x = w ? -x : x;
	return;
}

void dfs(int res, int lst) {
	if (!res) {
		ll s = 0;
		int st = a.size();
		for (R int i = 0; i < 1 << n; ++i)
			s += (_popcnt32(i) ^ n) & 1 ? -tmp[st][i] : tmp[st][i];
		vector<int> b = a;
		reverse(b.begin(), b.end());
		do {
			int tl = 0, t = 0;
			for (auto &k : b) {
				for (R int i = 1; i < k; ++i)
					t ^= 1 << tl, ++tl;
				++tl;
			}
			ans[t] = s;
		} while (next_permutation(b.begin(), b.end()));
		return;
	}
	for (R int i = 1; i <= min(lst, res); ++i) {
		a.push_back(i);
		for (R int j = 0; j < 1 << n; ++j)
			tmp[a.size()][j] = tmp[a.size() - 1][j] * h[i][j];
		dfs(res - i, i), a.pop_back();
	}
	return;
}

int main() {
	read(n);
	for (R int i = 0; i < n; ++i) {
		scanf("%s", s);
		for (R int j = 0; j < n; ++j)
			g[i][j] = s[j] - '0';
	}
	h[0][0] = 1;
	for (R int i = 0; i < n; ++i)
		f[1 << i][i] = 1, ++h[1][1 << i];
	for (R int i = 1; i < 1 << n; ++i) {
		int s = _popcnt32(i);
		if (s == 1) continue;
		for (R int j = 0; j < n; ++j)
			if (i & (1 << j)) {
				for (R int k = 0; k < n; ++k)
					if (k != j && (i & (1 << k)) && g[j][k])
						f[i][j] += f[i ^ (1 << j)][k];
				h[s][i] += f[i][j];
			}
	}
	for (R int i = 0; i < n; ++i)
		for (R int j = 0; j < n; ++j)
			for (R int k = 0; k < 1 << n; ++k)
				if (k & (1 << j))
					h[i][k] += h[i][k ^ (1 << j)];
	memcpy(tmp[0], h[0], sizeof (h[0]));
	dfs(n, n);
	for (R int i = 0; i < n; ++i)
		for (R int j = 0; j < 1 << n >> 1; ++j)
			if (j & (1 << i))
				ans[j ^ (1 << i)] -= ans[j];
	for (R int i = 0; i < 1 << n >> 1; ++i)
		printf("%lld ", ans[i]);
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/suwakow/p/12620453.html