P6037 Ryoku 的探索
基環樹
有兩種思路:
- 將環上一條邊斷開,轉化為樹上問題
- 先考慮環上,再考慮環上每個點構成的子樹。
考慮後者。首先基環樹上深度遍歷只會少走一條邊,所以考慮哪條邊沒被走。可以發現,基環樹上深度遍歷完後沒遍歷的邊一定在環上。那麼如果起點在環上,沒遍歷的邊一定是它在環上的兩條邊中美觀度最小的那條。怎麼理解呢?我們從起點,在環上走一定會選擇一個方向,沿這個方向繞環一圈後沒遍歷的就是起點在環上所連的另一條邊了。
考慮起點不在環上,那麼我們遍歷完不在環上的部分後就會走到環上,那麼結果就和環上那點一樣了。
先對所有邊權求和,難點是找到環後記錄環上答案(這裡我用了棧序列記錄從根到該節點的簡單路徑,然後找到環後直接遍歷棧序列即可),最後以環上每個點覆蓋其子樹即可。
複雜度 \(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;
}