link
二次扫描与换根,对于没有确定根的题目往往可以用到,可以减少一个枚举根的复杂度
题意
思路
1.考虑朴素解法,枚举根
s
s
s ,设 d[x]
为从 x 出发流向子树的最大流量,很容易求出dp方程(即代码中dfs部分)。对于每个根都进行一次dp,时间复杂度
O
(
n
2
)
O(n^2)
O(n2)。
2.考虑优化,首先任选一个源点
s
s
s 进行同上的dp,设 f[x]
表示把 x 作为根,流向整个水系的最大流量。则对于根节点
x
x
x , f[x] = d[x]
。考虑
x
x
x 的子节点
y
y
y ,f[y]
包涵两部分:
- 从
y
y
y 流向以
y
y
y 为根的子树的流量,即为
d[y]
。 - 从
y
y
y 沿着父节点
x
x
x 的河道,流向水系中其他部分的流量,设为
ad
。
容易得到 f[y] = f[x] + ad
,那么怎么求 ad
呢?
把 x 作为根节点的总流量为 f[x]
, 从 x 流向 y 的流量为 min(d[y], w[x,y])
,其中 w[x,y]
表示 xy 两点间河道容量。那么 x 流向除 y 以外的其他部分流量就是二者之差,于是把y作为根,先流到 x ,再流向其他部分的流量就是这个差与 w[x,y]
取最小值的结果。(前提是
x
,
y
x, y
x,y 度数均大于1)
if(in[cur] != 1 && in[to] != 1)
ad = min(f[cur] - min(e[i].dis, d[to]), e[i].dis);
else if(in[cur] != 1 && in[to] == 1)
ad = min(f[cur] - e[i].dis, e[i].dis);
else
ad = e[i].dis;
注意 dfs2(to, cur)
的搜索要放到更新完 to 之后。
代码
int n;
int head[maxn], cnt;
struct Edge {
int to, dis, next;
}e[maxn * 2];
bool vis[maxn];
int d[maxn], f[maxn];
int in[maxn];
void add(int u, int v, int w) {
++cnt;
e[cnt].to = v;
e[cnt].dis = w;
e[cnt].next = head[u];
head[u] = cnt;
}
int ans;
void dfs(int cur = 1, int fa = 0) {
for(int i = head[cur]; i; i = e[i].next) {
int to = e[i].to;
if(to == fa) continue;
dfs(to, cur);
if(in[to] == 1) {
d[cur] += e[i].dis;
}
else {
d[cur] += min(e[i].dis, d[to]);
}
}
}
void dfs2(int cur = 1, int fa = 0) {
for(int i = head[cur]; i; i = e[i].next) {
int to = e[i].to;
if(to == fa) continue;
f[to] = d[to];
int ad;
if(in[cur] != 1 && in[to] != 1)
ad = min(f[cur] - min(e[i].dis, d[to]), e[i].dis);
else if(in[cur] != 1 && in[to] == 1)
ad = min(f[cur] - e[i].dis, e[i].dis);
else
ad = e[i].dis;
f[to] += ad;
ans = max(ans, f[to]);
dfs2(to, cur);
}
}
void solve() {
cnt = 0;
ans = 0;
memset(f, 0, sizeof(f));
memset(d, 0, sizeof(d));
memset(in, 0, sizeof(in));
memset(head, 0, sizeof(head));
cin >> n;
for(int i = 1; i < n; i++) {
int u, v, w;
cin >> u >> v >> w;
in[u]++;
in[v]++;
add(u, v, w);
add(v, u, w);
}
dfs(1, 0);
f[1] = d[1];
ans = f[1];
dfs2(1, 0);
cout << ans << endl;
}