Accumulation Degree - 树形dp
题目链接 — South Central China 2008 Accumulation Degree
Description
\(给你一棵有n个结点的树,每一条边连接u_i和v_i\)
\(流量为w_i,你需要找出一个结点最为root,并且求出从root出发到其他所有叶子结点的流量最大值\)
\(数据范围1\leq n\leq 200000,并且\sum n \leq200000\)
Solution
\(如果你一来就想到网络流,说明你网络流没学好,树形dp相关的也没学好(笑)\)
\(我们要求出所有root中的最大值,毫无疑问肯定是要对每个结点都求一次的\)
\(关键在于思考,如果对每一个点作为root去dfs跑他的子树,肯定超时。那么有没有什么方法状态转移\)
\(在处理转移之前我们先维护一个root的最大流量,我们默认root为结点1,然后对他跑个dfs维护出flow[i]\)
\(flow[i]表示当前结点i所有子树流到流向他的值\)
\(转移公式 \sum_{所有子结点i} flow[p]+= min(w,flow[i]) 注:w为子结点到父节点的流量值,min是限流\)
\(在处理好以1为根结点的流量数组flow[]之后,我们来考虑向他的子结点转移\)
\(观察上面这副图,假设当前结点为u,子结点为v,设某点的流量为f[i].\)
\(则当前结点的最大流量 f[u] = x + min(y,z);\)
\(其子结点的最大流量为f[v] = z+min(x,y);\)
\(我们已知flow[u] = \sum_{所有子结点i}flow[i]\)
\(那么f[u]-min(flow[v],w)就表示u子结点v以外子结点的流量贡献,也就是上图的x\)
\(而子结点flow[v]即表示为上图的z,同时已知y即为w_{u->v}\)
\(所以可以得到f[v]=z+min(x,y)=flow[v]+min(f[u]-min(flow[v],w),w)\)
\(这样转移方程也就出来了\)
Code
#include<bits/stdc++.h>
typedef long long ll;
#define IOS ios::sync_with_stdio(0); cin.tie(0);
using namespace std;
const int maxn = 200010;
const int inf = 0x3f3f3f3f;
//思路:从每一个叶子结点跑,然后维护出到当前点最小的值,作为该节点对叶子结点的贡献值
struct node{
int v; int w;
};
int deg[maxn];
int sum[maxn];
int sum1[maxn];
vector<node>E[maxn];
void dfs(int p,int fa){
for(int i=0;i<(int)E[p].size();i++){
int v = E[p][i].v; int w = E[p][i].w;
if(v!=fa){
dfs(v,p);
if(deg[v] == 1){
sum[p] += w;
}else{
sum[p] += min(w,sum[v]);
}
}
}
return ;
}
void dfs1(int p,int fa){
for(int i=0;i<(int)E[p].size();i++){
int v = E[p][i].v; int w = E[p][i].w;
if(v!=fa){
if(deg[p] == 1){
sum1[v] = w;
}else{
sum1[v] = sum[v] + min((sum1[p]- min(sum[v],w)),w);
}
dfs1(v,p);
}
}
}
int main(){
IOS
int T;
cin>>T;
while(T--){
int n;
cin>>n;
for(int i=1;i<=n;i++) {
E[i].clear(),deg[i]=0;
sum1[i] = sum[i] = 0;
}
for(int i=1;i<=n-1;i++){
int u,v; int w;
cin>>u>>v>>w;
E[u].push_back((node){v,w});
E[v].push_back((node){u,w});
deg[u]++; deg[v]++;
}
dfs(1,0);
sum1[1] = sum[1];
dfs1(1,0);
int ans = -inf;
for(int i=1;i<=n;i++){
ans = max(ans,sum1[i]);
}
cout<<ans<<endl;
}
}