Living-Dream 系列笔记 第72期
U224225
一眼网络流,但是被诈骗了。
容易发现以每个点作为源点得到的答案均不相同,考虑换根 dp。
令 \(dp_i\) 表示以 \(i\) 为根的子树的最大流量。
初始:
\[\begin{cases}
dp_{cur}=0\ \ \ \ \ \text{when} \ cur \ 为非叶子\\
dp_{cur}=\infty\ \ \ \text{when} \ cur \ 为叶子\\
\end{cases}
\]
转移:
\[dp_{cur}=dp_{cur}+\min(dp_i,w)
\]
其中 \(cur\) 为 \(i\) 的父节点,\(w\) 为边 \(cur \to i\) 的边权。
(转移就解释了为什么初始状态要对 \(cur\) 是否为叶子进行讨论——若 \(i\) 为叶子节点且 \(dp_i\) 初始为 \(0\),则取\(\min\) 时会取到 \(0\) 而非 \(w\),从而导致答案错误)
令 \(f_i\) 表示以 \(i\) 为全局根的最大流量。
答案:\(\max\{f_i\}\)。
初始:\(f_1=dp_1\)。
转移:
\[\begin{cases}
f_i=dp_i+\min(w,f_{cur}-\min(w,dp_i)) \ \ \ \ \ \text{when} \ i \ 为非叶子 \\
f_i=\min(w,f_{cur}) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \text{when} \ i \ 为叶子
\end{cases}
\]
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
int t,n;
struct E{ int v,w; };
vector<E> G[N<<1];
int dp[N],f[N];
void dfs1(int cur,int fa){
if(cur!=1&&G[cur].size()==1)
dp[cur]=1e9;
for(auto i:G[cur]){
if(i.v==fa) continue;
dfs1(i.v,cur);
dp[cur]+=min(dp[i.v],i.w);
}
}
void dfs2(int cur,int fa){
for(auto i:G[cur]){
if(i.v==fa) continue;
//dfs2(i.v,cur);
if(dp[i.v]==1e9)
f[i.v]=min(i.w,f[cur]-min(i.w,0ll));
else
f[i.v]=dp[i.v]+min(i.w,f[cur]-min(i.w,dp[i.v]));
dfs2(i.v,cur);
}
}
signed main(){
cin>>t;
while(t--){
for(int i=1;i<=n;i++)
G[i].clear(),f[i]=dp[i]=0;
cin>>n;
for(int i=1,u,v,w;i<n;i++)
cin>>u>>v>>w,
G[u].push_back({v,w}),
G[v].push_back({u,w});
dfs1(1,0);
f[1]=dp[1];
dfs2(1,0);
int ans=0;
for(int i=1;i<=n;i++) ans=max(ans,f[i]);
cout<<ans<<'\n';
}
return 0;
}
P6419
钦定聚会地点为根时,只用考虑子树内的接送,换根启动!!
令 \(dp_i\) 表示送完以 \(i\) 为根的子树内的所有人再回到 \(i\) 所需的最短时间。
(这么定义状态是为了方便在根节点处进行转移,因为根节点不同子树间的接送一定要回到根,除最后一次外)
初始:\(dp_{cur}=0\)。
转移:
\[dp_{cur}=dp_{cur}+dp_{nxt}+2 \times w \ \ \ \ \ \operatorname{when} \ nxt \ 子树内有人要接送
\]
令 \(f_i\) 表示送完以 \(i\) 为全局根时送完所有人再回到 \(i\) 所需的最短时间。
答案:\(\min\{f_i-i \ 出发的最长链\}\)
(减去 \(i\) 出发的最长链是因为最后一次无需回到 \(i\),并且我们希望答案越小越好)
要求最长链,考虑像求树的中心那样维护 \(len_{1_i},len_{2_i},up_i\) 即可(具体请查阅往期笔记,此处不再赘述)。
转移:
\[f_{nxt}=dp_{nxt}+(f_{cur}-dp_{nxt}-2 \times w \times g_1)+2 \times w \times g_2
\]
其中:
\[\begin{cases}
g_1=0 \ \ \ \ \ \text{when} \ siz_{nxt}=0\\
g_1=1 \ \ \ \ \ \text{when} \ siz_{nxt}>0
\end{cases}
\]
\[\begin{cases}
g_2=0 \ \ \ \ \ \text{when} \ k-siz_{nxt}=0\\
g_2=1 \ \ \ \ \ \text{when} \ k-siz_{nxt}>0
\end{cases}
\]
(\(siz_{nxt}\) 表以 \(nxt\) 为根的子树大小)
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+5;
int n,k;
struct E{ int v,w; };
vector<E> G[N<<1];
int dp[N],f[N],siz[N];
int len1[N],len2[N],up[N];
void dfs1(int cur,int fa){
for(auto i:G[cur]){
if(i.v==fa) continue;
dfs1(i.v,cur);
siz[cur]+=siz[i.v];
if(siz[i.v]!=0){
dp[cur]+=dp[i.v]+i.w*2;
if(len1[cur]<len1[i.v]+i.w)
len2[cur]=len1[cur],len1[cur]=len1[i.v]+i.w;
else if(len2[cur]<len1[i.v]+i.w)
len2[cur]=len1[i.v]+i.w;
}
}
}
void dfs2(int cur,int fa){
for(auto i:G[cur]){
if(i.v==fa) continue;
if(k-siz[i.v]!=0){
up[i.v]=up[cur]+i.w;
if(len1[cur]!=len1[i.v]+i.w)
up[i.v]=max(up[i.v],len1[cur]+i.w);
else
up[i.v]=max(up[i.v],len2[cur]+i.w);
}
f[i.v]=dp[i.v]+(f[cur]-dp[i.v]-2*i.w*(siz[i.v]!=0))+2*i.w*(k-siz[i.v]!=0);
dfs2(i.v,cur);
}
}
signed main(){
ios::sync_with_stdio(0);
cin>>n>>k;
for(int i=1,u,v,w;i<n;i++)
cin>>u>>v>>w,
G[u].push_back({v,w}),
G[v].push_back({u,w});
for(int i=1,x;i<=k;i++)
cin>>x,siz[x]++;
dfs1(1,0);
f[1]=dp[1];
dfs2(1,0);
for(int i=1;i<=n;i++)
cout<<f[i]-max(up[i],len1[i])<<'\n';
return 0;
}