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