Living-Dream 系列笔记 第72期

Posted on 2024-08-03 14:37  _XOFqwq  阅读(6)  评论(0编辑  收藏  举报

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