P6037 Ryoku 的探索

Fire_Raku發表於2024-04-17

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;
}

相關文章