洛谷 P8867 [NOIP2022] 建造军营 题解
建造军营
Description
给定一个 \(n\) 个点 \(m\) 条边的无向连通图。需要选择若干个点(不能不选)建造军营,并选择若干条边派兵看守。
被看守的边不会被切断,未看守的边可能被切断。要求无论切断哪一条边,图中任意两个军营之间仍然连通。
对选点集并选边集求方案数,对 \(10^9 + 7\) 取模。
\(1 \le n \le 5 \times 10^5,\ 1 \le m \le 10^6\)。
Solution
边双建树
注意到破坏仅存在于单边,因此考虑单边破坏的连通性的影响,想到 eDCC 问题。
我们熟知,在一个 eDCC 中,删去任意一条边仍能保证其中的所有点联通。因此,在原图中砍掉一条边时,如果这条边在某个 eDCC 中,那么这个砍一定不会影响原图的连通性。进一步地,只有砍掉连接两 eDCC 的边(即桥)才会使得连通性发生变化。所以最终的答案一定要选中所有桥。
根据上面的思路,联想到 eDCC 缩点,将原图变为一棵树。其中每个节点代表一个 eDCC,并具有点数 \(v\) 和边数 \(e\) 的两个属性。现在问题转化为在转化出的树上 DP 计数。
定义 \(dp_{u,0/1}\) 表示 \(u\) 子树内有/没有被点亮的点时的方案数。注意到这样定义会算重(例如 \(u \to fa\) 与 \(fa \to u\) 的互相制约),因此考虑只在子树内计数。具体地,强制不选边 \((u,fa)\),且强制不点亮 \(u\) 子树外的点。
对于一个 eDCC,其中不点亮任何点的方案数为 \(2^e\),存在点亮的点的方案数为 \(2^{v+e} - 2^e\)(根据容斥原理可得)。
因此 \(dp\) 数组的初始值为
状态转移
如果 \(u\) 子树内不存在点亮的点,那么选择权力交付于 \(u\) 所属的 eDCC 内的所有边,以及 \(u \to \forall v\) 的所有直接连边,每条边都有点亮与不点亮两种情况,因此
如果 \(u\) 子树内存在点亮的点,那么对于其新增的一个儿子 \(v\),假使 \(v\) 前面的儿子的子树内都不存在点亮的点,而 \(v\) 子树内刚好存在点亮的点,那么
如果 \(v\) 前面的儿子的子树内存在点亮的点,那么 \(v\) 子树内则不一定非要存在点亮的点,即
将上两式相加,得
答案计算
考虑 \(u\) 子树外的情况。刚才钦定了 \(u\) 子树外的所有点不能被点亮,所以考虑 \(u\) 子树外的边的点亮与否。
具体地,设 \(s_u\) 表示 \(u\) 子树内的总边数(包含树边与 eDCC 内的边)。容易写出
对于 \(u\) 子树外的边,要么被点亮,要么不被点亮,即
特别地,对于 \(1\) 号节点,有
最后输出 \(ans\) 即可,记得取模。
Code
#include <bits/stdc++.h>
#define int long long
#define inf 1e18
#define debug cout << '!';
#define filein(x) freopen(#x".in", "r", stdin);
#define fileout(x) freopen(#x".out", "w", stdout);
#define file(x) filein(x) fileout(x)
// #define Fast_IO
using namespace std;
#ifdef Fast_IO
inline int read() {
int x = 0, f = 1; char c = getchar();
while (c < '0' or c > '9') { if (c == '-') f = -1; c = getchar(); }
while (c >= '0' and c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
void write(int x) {
if (x < 0) putchar('-'), x = -x;
if (x > 9) write(x / 10);
putchar(x % 10 + '0'); return;
}
#endif
const int N = 5e5 + 5, mod = 1e9 + 7;
inline int qpow(int x, int y) {
int res = 1;
while (y) {
if (y & 1) res = res * x % mod;
x = x * x % mod;
y >>= 1;
}
return res;
}
int n, m;
int dfn[N], low[N], bel[N];
int dp[N][2], ver[N], edg[N], sum[N], ans;
int ecnt, dfncnt, edcccnt;
bool ins[N];
struct Node { int v, eid; }; vector<Node> g[N];
vector<int> h[N];
stack<int> stk;
void Tarjan(int u, int faid) {
dfn[u] = low[u] = ++dfncnt;
stk.push(u);
ins[u] = 1;
for (auto [v, eid]: g[u]) {
if (eid == faid) continue;
if (not dfn[v]) {
Tarjan(v, eid);
low[u] = min(low[u], low[v]);
} else if (ins[v]) {
low[u] = min(low[u], dfn[v]);
}
}
if (dfn[u] == low[u]) {
edcccnt++;
int top;
do {
top = stk.top(); stk.pop();
ins[top] = 0;
bel[top] = edcccnt;
ver[edcccnt]++;
} while (top != u);
}
}
void Restruct() {
for (int u = 1; u <= n; u++) {
for (auto [v, _]: g[u]) {
if (bel[u] != bel[v]) {
h[bel[u]].push_back(bel[v]);
} else {
edg[bel[u]]++;
}
}
}
}
void DFS1(int u, int fa) {
sum[u] = edg[u];
for (auto v: h[u]) {
if (v == fa) continue;
DFS1(v, u);
sum[u] += sum[v] + 1;
}
}
void DFS2(int u, int fa) {
for (auto v: h[u]) {
if (v == fa) continue;
DFS2(v, u);
dp[u][1] = (((dp[u][1] * (2 * dp[v][0] + dp[v][1]) % mod) % mod) + (dp[u][0] * dp[v][1] % mod)) % mod;
dp[u][0] = (dp[u][0] * (2 * dp[v][0]) % mod) % mod;
}
if (u == 1) {
ans = (ans + dp[u][1]) % mod;
} else {
ans = (ans + dp[u][1] * qpow(2, sum[1] - sum[u] - 1)) % mod;
}
}
void DP_init() {
for (int i = 1; i <= edcccnt; i++) {
edg[i] /= 2;
dp[i][0] = qpow(2, edg[i]);
dp[i][1] = qpow(2, edg[i] + ver[i]) - dp[i][0];
}
}
signed main() {
cin.tie(0) -> sync_with_stdio(0);
cin >> n >> m;
while (m--) {
int u, v; cin >> u >> v;
ecnt++;
g[u].push_back({v, ecnt}), g[v].push_back({u, ecnt});
}
for (int i = 1; i <= n; i++) {
if (not dfn[i]) {
Tarjan(i, -1);
}
}
Restruct();
DP_init();
DFS1(1, -1);
DFS2(1, -1);
cout << ans % mod << '\n';
return 0;
}

浙公网安备 33010602011771号