Loading

P6037 Ryoku 的探索 (基环树)

P6037 Ryoku 的探索

基环树

有两种思路:

  1. 将环上一条边断开,转化为树上问题
  2. 先考虑环上,再考虑环上每个点构成的子树。

考虑后者。首先基环树上深度遍历只会少走一条边,所以考虑哪条边没被走。可以发现,基环树上深度遍历完后没遍历的边一定在环上。那么如果起点在环上,没遍历的边一定是它在环上的两条边中美观度最小的那条。怎么理解呢?我们从起点,在环上走一定会选择一个方向,沿这个方向绕环一圈后没遍历的就是起点在环上所连的另一条边了。

考虑起点不在环上,那么我们遍历完不在环上的部分后就会走到环上,那么结果就和环上那点一样了。

先对所有边权求和,难点是找到环后记录环上答案(这里我用了栈序列记录从根到该节点的简单路径,然后找到环后直接遍历栈序列即可),最后以环上每个点覆盖其子树即可。

复杂度 \(O(n)\)

#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define fi first
#define se second
#define pb push_back

typedef long long i64;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
const int N = 1e6 + 10;
int n, cnt;
i64 ans, f[N];
struct node {
	int to, nxt;
	i64 w, p;
} e[N << 1];
int h[N];
void add(int u, int v, int w, int p) {
	e[++cnt].to = v;
	e[cnt].nxt = h[u];
	e[cnt].w = w, e[cnt].p = p;
	h[u] = cnt;
}
bool flg;
int top;
bool ins[N], vis[N];
pii st[N];
void dfs(int u, int fa, int num) {
	st[++top] = {u, num};
	ins[u] = 1;
	for(int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].to;
		if(v == fa) continue;
		if(ins[v] && !flg) {
			int h = 1;
			while(h <= top && st[h].fi != v) h++;
			for(int j = h; j <= top; j++) {
				vis[st[j].fi] = 1;
				int l = (j != h) ? st[j].se : i, r = (j != top) ? st[j + 1].se : i;
				if(e[l].p > e[r].p) f[st[j].fi] = e[r].w;
				else f[st[j].fi] = e[l].w;
			}
			flg = 1;
		}
		else if(!ins[v]) dfs(v, u, i);
	}
	top--; //注意出栈
}
void upd(int u, int fa, int rt) {
	f[u] = f[rt];
	for(int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].to;
		if(v == fa || vis[v]) continue;
		upd(v, u, rt);
	}
}
void Solve() {
	std::cin >> n;
	for(int i = 1; i <= n; i++) {
		int u, v, w, p;
		std::cin >> u >> v >> w >> p;
		add(u, v, w, p), add(v, u, w, p);
		ans += w;
	}
	dfs(1, 0, 0);
	for(int i = 1; i <= n; i++) {
		if(vis[i]) {
			upd(i, 0, i);
		}
	}
	for(int i = 1; i <= n; i++) {
		std::cout << ans - f[i] << "\n";
	}
}
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
	Solve();

	return 0;
}
posted @ 2024-04-17 20:52  Fire_Raku  阅读(7)  评论(0编辑  收藏  举报