UOJ #37. 【清华集训2014】主旋律
UOJ #37. 【清华集训2014】主旋律
看到 \(n\le 15\) 于是可以考虑使用状压DP。设
- \(f_S\) 表示点集 \(S\) 内组成强联通图的方案数;
- \(g_S\) 表示点集 \(S\) 内组成非强联通图的方案数。
则有 \(f_S=2^{\operatorname{cnt}(S,S)}-g_S\) 其中 \(\operatorname{cnt}(S,T)=\sum\limits_{(x,y)\in E}[x\in S\and y\in T]\) 表示点集 \(S\) 连向点集 \(T\) 的边数。
考虑如何求 \(g_S\)。一个非强联通图 \(G\) 可能由若干强联通子图组成,也可能由若干强联通子图和若干DAG子图构成。注意到如果 \(G\) 包含DAG子图,那么 \(G\) 中一定存在若干出度为 \(0\) 极大强联通块。
我们设 \(h_S\) 表示点集 \(S\) 组成若干个强联通图的方案数。则有 \(h_S=f_S+\sum\limits_{T\sub S,x\in T}f_{T}h_{S/T}\) 其中 \(x\) 为点集合 \(S\) 中的任意一点,枚举条件中的 \(x\in T\) 是确保枚举子集计算时不会重复。在求出 \(h_S\) 之后,我们便可以考虑 \(g_S\) 了。我们枚举 \(S\) 的子集 \(T\),设补集 \(\hat S=S/T\),将子集 \(T\) 钦定为出度为 \(0\) 的极大强联通块集合。子集 \(T\) 内的点构成若干个强联通子图的方案数为 \(h_T\),对于 \(\hat S\rightarrow T\) 和 \(\hat S\rightarrow \hat S\) 的边随便连,于是 \(g\) 就可以转移了,转移方程式长 \(g_S=\sum\limits_{T\sube S\and T\neq\empty}2^{\operatorname{cnt}(S/T,S)}h_{T}\) 这样。
但我们发现这样会算重,导致算重的原因是我们在处理 \(\hat S\rightarrow S\) 的边时并没有保证 \(\hat S\) 内不会出现出度为 \(0\) 的极大强联通块。于是考虑使用容斥去重,设 \(h_{T,k}\) 表示将集合 \(T\) 分为恰好 \(k\) 个极大强联通块的方案数,则使用容斥知 \(g_S=\sum\limits_{T\sube S\and T\neq\empty}2^{\operatorname{cnt}(S/T,S)}\sum\limits_{k\ge 1}(-1)^{k-1}h_{T,k}\)。不考虑计算 \(\operatorname{cnt}(S,T)\) 复杂度的情况下,这个转移是 \(\mathcal O(3^nn)\) 的;预处理 \(h_{T,k}\) 是 \(\mathcal O(3^nn)\) 的,因此我们就得到了一个 \(\mathcal O(3^nn)\) 的转移。
考虑优化。我们发现记录 \(h_{T,k}\) 太浪费了;当 \(k\) 为奇数时,\(h_{T,k}\) 的贡献均为 \(1\),而当 \(k\) 为偶数时,\(h_{T,k}\) 的贡献均为 \(-1\)。于是我们考虑重新设计状态 \(h_S\)。重新定义 \(h_{S}=\sum\limits_{k\ge 1}(-1)^{k-1}h_{S,k}\),则有转移 \(h_S=f_S-\sum\limits_{T\sub S,x\in T}f_{T}h_{S/T}\) 其中 \(x\) 是点集合 \(S\) 中的任意一点。这样 \(g\) 的转移就重新变成了 \(g_S=\sum\limits_{T\sube S\and T\neq\empty}2^{\operatorname{cnt}(S/T,S)}h_{T}\)。两者的转移都是 \(\mathcal O(3^n)\),于是DP转移的复杂度就是 \(\mathcal O(3^n)\)。
最后我们考虑如何求 \(\operatorname{cnt}(S,T)\)。注意到直接开数组存下 \(\operatorname{cnt}(S,T)\) 会爆空间,而当 \(T_1\cap T_2=\empty\) 时有 \(\operatorname{cnt}(S,T_1)+\operatorname{cnt}(S,T_2)=\operatorname{cnt}(S,T_1\cup T_2)\),于是我们直接使用分块拆位优化空间的trick即可。
参考代码
#include <bits/stdc++.h>
using namespace std;
static constexpr int mod = 1e9 + 7;
inline int add(int x, int y) { return x += y - mod, x + (x >> 31 & mod); }
inline int sub(int x, int y) { return x -= y, x + (x >> 31 & mod); }
inline int mul(int x, int y) { return (int64_t)x * y % mod; }
inline void add_eq(int &x, int y) { x += y - mod, x += (x >> 31 & mod); }
inline void sub_eq(int &x, int y) { x -= y, x += (x >> 31 & mod); }
inline void mul_eq(int &x, int y) { x = (int64_t)x * y % mod; }
int qpow(int x, int y) { int r = 1; for (; y; y >>= 1, mul_eq(x, x)) if (y & 1) mul_eq(r, x); return r; }
inline int lowbit(uint32_t x) { return x & (x - 1); }
inline int highbit(uint32_t x) { return 1 << (31 - __builtin_clz(x)); }
static constexpr int N = 15, M = 215, B = 7;
int n, m, pw2[M], e[N][N];
int e00[1 << B][1 << B], e11[1 << N - B][1 << N - B];
int e01[1 << B][1 << N - B], e10[1 << N - B][1 << B];
int f[1 << N], g[1 << N], h[1 << N];
inline int calc(int s1, int s2) {
return e00[s1 & (1 << B) - 1][s2 & (1 << B) - 1]
+ e01[s1 & (1 << B) - 1][s2 >> B]
+ e10[s1 >> B][s2 & (1 << B) - 1]
+ e11[s1 >> B][s2 >> B];
} // calc
int main(void) {
scanf("%d%d", &n, &m); pw2[0] = 1;
for (int i = 1; i <= m; ++i) pw2[i] = add(pw2[i - 1], pw2[i - 1]);
for (int i = 0, u, v; i < m; ++i)
scanf("%d%d", &u, &v), --u, --v, e[u][v]++;
for (int s1 = 0; s1 < (1 << B); ++s1) for (int s2 = 0; s2 < (1 << B); ++s2)
for (int x1 = 0; x1 < n; ++x1) for (int x2 = 0; x2 < n; ++x2)
if ((s1 & (1 << x1)) != 0 && (s2 & (1 << x2)) != 0) e00[s1][s2] += e[x1][x2];
for (int s1 = 0; s1 < (1 << B); ++s1) for (int s2 = 0; s2 < (1 << N - B); ++s2)
for (int x1 = 0; x1 < n; ++x1) for (int x2 = 0; x2 < n; ++x2)
if ((s1 & (1 << x1)) != 0 && ((s2 << B) & (1 << x2)) != 0) e01[s1][s2] += e[x1][x2];
for (int s1 = 0; s1 < (1 << N - B); ++s1) for (int s2 = 0; s2 < (1 << B); ++s2)
for (int x1 = 0; x1 < n; ++x1) for (int x2 = 0; x2 < n; ++x2)
if (((s1 << B) & (1 << x1)) != 0 && (s2 & (1 << x2)) != 0) e10[s1][s2] += e[x1][x2];
for (int s1 = 0; s1 < (1 << N - B); ++s1) for (int s2 = 0; s2 < (1 << N - B); ++s2)
for (int x1 = 0; x1 < n; ++x1) for (int x2 = 0; x2 < n; ++x2)
if (((s1 << B) & (1 << x1)) != 0 && ((s2 << B) & (1 << x2)) != 0) e11[s1][s2] += e[x1][x2];
for (int s = 1; s < (1 << n); ++s) {
for (int t = (s - 1) & s; t & highbit(s); t = (t - 1) & s)
sub_eq(h[s], mul(f[t], h[s ^ t]));
for (int t = s; t; t = (t - 1) & s)
add_eq(g[s], mul(pw2[calc(s ^ t, s)], h[t]));
f[s] = sub(pw2[calc(s, s)], g[s]); add_eq(h[s], f[s]);
} printf("%d\n", f[(1 << n) - 1]);
exit(EXIT_SUCCESS);
} // main
浙公网安备 33010602011771号