这本来是一道容斥好题,但是被 * 一样的子任务分配毁了……
(其实是因为一万个人过了 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) 时正确,则枚举 (kin[0, m-2]) 后,每个 (xoperatorname{and} 2^{m-1} e 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,复杂度大约是 (inom{frac{n}{2}}{n}^2) 级别,无法接受。
但是注意到我们要求的是给每个人分配恰好一个组的方案数。由于 (sum a_i=n),所以如果某人被分配了多于一个组,则必然有人没有被分配到组。因此它的方案数等价于给每个人分配至少一个组的方案数。设 (h_{k, s}) 表示只在 (s_i=1) 的人中找到一条长度为 (k) 的哈密顿路径的方案数,显然 (h_{k, s}=sum_{tsubseteq s} g_{k, t})。设 (d_s=prod h_{a_i, s}),则只需要容斥就可以得到 (a) 对应的答案。此处容斥类似 [ZJOI2016] 小星星。
如果暴力枚举 (a),再暴力枚举 (a_i) 计算,复杂度为 (2^nsum |a|),但是如果在搜索 (a) 的过程中,即时算出每层对应的 (d),即可优化到 (2^n p_n),因为搜索树上分叉的数量等于叶节点数量。前面求 (h_{k, s}) 时需要对每个 (k) 做一遍高维前缀和(子集和),因此复杂度是 (Oleft(2^n(p_n+n^2) ight))。
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;
}