「NOIP2022」建造军营 题解
前言
题目链接:洛谷。
题意简述
yzh 送你一张 \(n\) 个点 \(m\) 条边的无向连通图,你可以决定选择 \(n\) 个点中若干个、\(m\) 条边中若干条,方案数为 \(2^n2^m\)。在你操作后,yzh 会任意挑选一条边,如果这条边没有被你选中,那么就要断开这条边,否则什么事也没发生。你需要保证无论 yzh 怎么选择,你选出的点集在操作后是连通的。求你选择的方案数,对 \(998244353\) 取模。
\(1 \leq n \leq 5 \times 10^5\),\(n - 1 \leq m \leq 10^6\),保证图连通,无自环、重边。
题目分析
这么大一张无向图,要么考虑 tarjan 缩点,要么考虑随便建出一棵 DFS 树,分为树边、非树边考虑。对于此题,两种做法均可,作者仅介绍更为好像的 tarjan 缩点做法,DFS 树方法留给读者思考。
发现,yzh 选择的边会导致原图不连通,当且仅当选择了图上的桥。用 tarjan 把边双缩点后,图变成了一棵树。问题此时转变成了,在 \(n'\) 点中选出一个子集 \(S\),若选择了 \(u \in S\),在原问题上有 \(2^{\operatorname{siz}(u)} - 1\) 中选择方案(\(\operatorname{siz}(u)\) 表示双连通分量 \(u\) 在原图中点数),表示 \(u\) 这个边双中选择了若干个不为空的点;记 \(S\) 构成的虚树(即最小的包含 \(S\) 的连通块)中有 \(k\) 条边,那么我们必须选择这 \(k\) 条边,才能保证 \(S\) 连通,而剩下 \((n' - 1) - k\) 和点双中的 \(m - (n' - 1)\) 条,共 \(m - k\) 条边随便选择,方案数为 \(2^{m - k}\)。
形式化地表示如下:
这样一坨东西应该需要树形 DP 解决。
从 \(S\) 计数显然不好算,考虑从最小连通块角度切入。我们枚举这个最小连通块,连通块最外层的点(即度为 \(1\) 的点)\(u\) 必须至少选择了 \(\operatorname{siz}(u)\) 中的一个,方案数为 \(2^{\operatorname{siz}(u)} - 1\),而内部节点可以随便选择,方案数为 \(2^{\operatorname{siz}(u)}\)。我们此时不用纠结最小连通块的边数 \(\operatorname{calc}(S)\) 为多少了,只需要在 DP 的时候,如果选择了某一条边到连通块中,乘上 \(2^{-1}\),最后的答案再乘以 \(2^m\) 即可。这样枚举能够包含原先所有情况。
那么 DP 的状态也很容易想了,用 \(f_{u, 0/1/2}\) 分别表示 \(u\) 为某一个连通块最浅的点,其有 \(0\) 个儿子、恰 \(1\) 个儿子、\(2\) 个及以上儿子的答案。拥有 \(0\) 个儿子的、或者深度最浅并且恰有 \(1\) 个儿子的结点,即为最外层的点。
\(f_{u, 0}\) 最简单,不需要转移,为 \(2^{\operatorname{siz}(u)} - 1\)。
\(f_{u, 1}\) 继承某一个儿子的答案,乘上 \(u\) 的方案数,根据加法原理累加即可。注意选中了 \(u\) 和其儿子间的一条边,需要乘上 \(2^{-1}\) 的系数。为了方便,我们记 \(g_u = f_{u, 0} + f_{u, 1} + f_{u, 2}\)。
\(f_{u, 2}\) 直接来不好做,需要和 \(f_{u, 1}\) 一起算。我们顺次枚举孩子 \(v\),先让 \(f_{u, 2} \gets f_{u, 2} + (f_{u, 1} + f_{u, 2}) \cdot 2^{-1} \cdot g_v\),再 \(f_{u, 1} \gets f_{u, 1} + 2^{\operatorname{siz}(u)} \cdot 2^{-1} \cdot g_v\)。前者表示讨论是否选择 \((u, v)\) 再根据加法原理累加。
如何统计答案呢?我们考虑在联通块最浅的那个点统计。\(f_{u, 0 / 2}\) 显然直接累加即可,但是 \(f_{u, 1}\) 需要保证 \(u\) 至少选定了一个点,所以需要加上 \(f_{u, 1}\) 后减去每个非根的 \(u\) 的 \(2^{-1} g_u\)。
此时树形 DP 时间复杂度已经是 \(\mathcal{O}(n)\) 的了,总的时间复杂度为 \(\mathcal{O}(n + m)\) 足以通过本题。不过在写题解的过程中,发现可以进一步优化状态。
我们发现,完全不需要记 \(f_{u, 2}\),所以我们状态 \(f_{u, 0/1}\) 变成了 \(u\) 是否有孩子的答案。
这个简单容斥以下等价于下者:
于是又可以解决问题了。我们进一步发现,在转移的时候,自始至终都在使用 \(g_v\),所以可以把 \(f_{u, 0/1}\) 合并起来。
代码
轻松最优解。
#include <cstdio>
#include <iostream>
using namespace std;
const int mod = 1e9 + 7, inv2 = (mod + 1) >> 1;
inline int add(int a, int b) { return a >= mod - b ? a + b - mod : a + b; }
inline int sub(int a, int b) { return a < b ? a - b + mod : a - b; }
inline int mul(int a, int b) { return 1ll * a * b % mod; }
const int N = 500010, M = 1000010;
struct Graph {
struct node {
int to, nxt;
} edge[M << 1];
int head[N], tot = 1;
inline void add(int u, int v) {
edge[++tot] = { v, head[u] };
head[u] = tot;
}
inline node & operator [] (int x) {
return edge[x];
}
} xym, yzh;
int n, m;
int dfn[N], low[N], timer;
int stack[N], top;
int sccno[N], scc_cnt, siz[N];
void tarjan(int u, int fr) {
dfn[u] = low[u] = ++timer, stack[++top] = u;
for (int i = xym.head[u]; i; i = xym[i].nxt) {
if ((i ^ 1) == fr) continue;
int v = xym[i].to;
if (!dfn[v]) tarjan(v, i), low[u] = min(low[u], low[v]);
else low[u] = min(low[u], dfn[v]);
}
if (low[u] >= dfn[u]) {
++scc_cnt;
do {
int v = stack[top--];
sccno[v] = scc_cnt;
++siz[scc_cnt];
} while (stack[top + 1] != u);
}
}
int pw[M], f[N], ans;
void dfs(int u, int fa) {
int one = 1;
for (int i = yzh.head[u]; i; i = yzh[i].nxt) {
int v = yzh[i].to;
if (v == fa) continue;
dfs(v, u);
ans = sub(ans, mul(inv2, f[v]));
one = mul(one, add(mul(inv2, f[v]), 1));
}
f[u] = mul(sub(one, 1), pw[siz[u]]);
f[u] = add(f[u], sub(pw[siz[u]], 1));
ans = add(ans, f[u]);
}
signed main() {
#ifndef XuYueming
freopen("barrack.in", "r", stdin);
freopen("barrack.out", "w", stdout);
#endif
scanf("%d%d", &n, &m);
for (int i = 1, u, v; i <= m; ++i) {
scanf("%d%d", &u, &v);
xym.add(u, v), xym.add(v, u);
}
tarjan(1, 0);
for (int u = 1; u <= n; ++u)
for (int i = xym.head[u]; i; i = xym[i].nxt) {
int v = xym[i].to;
if (sccno[u] == sccno[v]) continue;
yzh.add(sccno[u], sccno[v]);
}
pw[0] = 1;
for (int i = 1; i <= max(n, m); ++i) pw[i] = add(pw[i - 1], pw[i - 1]);
dfs(1, 0), ans = mul(ans, pw[m]);
printf("%d", ans);
return 0;
}
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/18523756。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。