洛谷 P8867 [NOIP2022] 建造军营 题解

建造军营

link

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\) 数组的初始值为

\[\begin{cases} dp_{u,0} = 2^{e_u} \\ dp_{u,1} = 2^{v_u + e_u} - 2^{e_u} \end{cases} \]

状态转移

如果 \(u\) 子树内不存在点亮的点,那么选择权力交付于 \(u\) 所属的 eDCC 内的所有边,以及 \(u \to \forall v\) 的所有直接连边,每条边都有点亮与不点亮两种情况,因此

\[dp_{u,0} \leftarrow 2^{e_u} \times \prod_{v \in son_u} (2 \times dp_{v,0}) \]

如果 \(u\) 子树内存在点亮的点,那么对于其新增的一个儿子 \(v\),假使 \(v\) 前面的儿子的子树内都不存在点亮的点,而 \(v\) 子树内刚好存在点亮的点,那么

\[dp_{u,1} \leftarrow dp_{u,0} \times dp_{v,1} \]

如果 \(v\) 前面的儿子的子树内存在点亮的点,那么 \(v\) 子树内则不一定非要存在点亮的点,即

\[dp_{u,1} \overset \times \leftarrow 2 \times dp_{v,0} + dp_{v,1} \]

将上两式相加,得

\[dp_{u,1} \leftarrow [dp_{u,0} \times dp_{v,1} + dp_{u,1} \times (2 \times dp_{v,0} + dp_{v,1})] \]

答案计算

考虑 \(u\) 子树外的情况。刚才钦定了 \(u\) 子树外的所有点不能被点亮,所以考虑 \(u\) 子树外的边的点亮与否。

具体地,设 \(s_u\) 表示 \(u\) 子树内的总边数(包含树边与 eDCC 内的边)。容易写出

\[s_u = e_u + \sum_{v \in son_u} (s_v + 1) \]

对于 \(u\) 子树外的边,要么被点亮,要么不被点亮,即

\[ans \overset + \leftarrow 2^{s_1 - s_u - 1} \times dp_{u,1} \]

特别地,对于 \(1\) 号节点,有

\[ans \overset + \leftarrow dp_{1,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;
}
posted @ 2026-06-13 15:36  L-Coding  阅读(28)  评论(0)    收藏  举报