题解:P11714 [清华集训 2014] 主旋律
注:本文参考了洛谷题解,所以有的地方思路没有那么自然,因为我也不知道怎么想到的,就当积累经验了。膜拜 Genius_Star /bx /bx /bx
不太一样的 DAG 容斥题。
直接统计强连通显然是不现实的,应容斥一下求不是强连通的方案数,所以可以联想到 DAG 容斥。
但是这道题不太一样,所以我们设 \(f_s\) 表示点集 \(s\) 是强连通的方案数,那么转移可以用类似 DAG 容斥的方法:
其中 \(\mathbb{calc}(u,v)\) 表示从点集 \(u\) 出发到点集 \(v\) 的边数,可以通过预处理 \(cnt_{x,u}\) 表示从 \(x\) 出发到点集 \(u\) 的边数,查询 \(O(n)\)。\(val\) 表示 \(t\) 中连通块的数量。
这就是个普通的 DAG 容斥,但是问题是若 \(t\) 内部的边的删除方案不同,\(val\) 也会改变。这很麻烦,所以我们考虑设 \(g_t\) 为点集 \(t\) 的贡献,即 \(t\) 中有奇数个强连通分量的方案数减有偶数个强连通分量的方案数,并凭感觉列出一个转移:
这个也是容斥,但仔细思考或写出代码尝试后你会发现这个算重复了,因为无法区分每种方案的强连通分量,于是直接假定 \(\mathbb{lowbit}(s)\) 是 \(t\) 中的强连通分量就行了,即添加条件 \(\mathbb{lowbit}(s)=\mathbb{lowbit}(t)\)。
列出 \(f,g\) 的转移后你会发现 \(f_s,g_s\) 好像互相依赖。我们审视 \(f_s\) 的转移,后面减掉的那一坨是 \(s\) 不是强连通的方案,但你算 \(g_s\) 时算了 \(s\) 是强连通的方案,而且恰好就是通过 \(f_s\) 算的。那么如果你暂且让 \(g_s=-\sum_{t\subset s} f_t \cdot g_{s-t}\),此时 \(g_s\) 就没有考虑 \(s\) 强连通的方案数,可以直接去转移 \(f_s\),转移完 \(f_s\) 在把 \(f_s\) 加在 \(g_s\) 上就行了。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 18, maxm = 220, maxc = (1 << 15) + 5;
const int mod = 1e9 + 7;
int n, m, cnt[maxn][maxc];
int f[maxc], g[maxc];
int pow2[maxm];
int calc(int u, int v) {
int res = 0;
for(int i = 1; i <= n; i++) if(u & (1 << (i - 1))) res += cnt[i][v];
return res;
}
int main() {
scanf("%d%d", &n, &m);
pow2[0] = 1;
for(int i = 1; i <= m; i++) pow2[i] = 2ll * pow2[i - 1] % mod;
int u, v, t = (1 << n) - 1;
for(int i = 1; i <= m; i++) {
scanf("%d%d", &u, &v);
for(int j = 1; j <= t; j++) if(j & (1 << (v - 1))) cnt[u][j]++;
}
for(int i = 1; i <= t; i++) {
for(int j = (i - 1) & i; j; j = (j - 1) & i) {
if((i & -i) != (j & -j)) continue;
g[i] -= 1ll * f[j] * g[i ^ j] % mod;
if(g[i] < 0) g[i] += mod;
}
f[i] = pow2[calc(i, i)];
for(int j = i; j; j = (j - 1) & i) {
f[i] -= 1ll * g[j] * pow2[calc(i ^ j, i)] % mod;
if(f[i] < 0) f[i] += mod;
}
g[i] += f[i];
if(g[i] >= mod) g[i] -= mod;
}
printf("%d\n", f[t]);
return 0;
}

浙公网安备 33010602011771号