poj 3585 二次扫描+换根dp

题目链接: poj 3585 

借鉴自《算法竞赛进阶指南》

这题应该算换根dp的入门题吧 

首先我们进行第一次扫描 可以任选一个根x 算出以x为源点的最大流量是多少  

D_{s}[x]表示以x为根的子树中,把x作为源点 从x流向子树的最大流量是多少 我们用d[i]表示一个点的入度  有如下dp方程

D_{s}[x]=\sum_{y\in Son(x)} \binom{min(D_{s}[y],c[x][y])\rightarrow (d[y]>1)}{c[x][y]\rightarrow (d[y]=1)}

第二次扫描时我们先把根节点的答案计入(因为第一次扫描已经处理好了)  当我们要遍历一个子节点的时候(设为y)

我们要进行换根操作  如何换根呢  我们想  x的流量包括了y和其他x的子节点  那我要以y为根的话  x得是y的子节点  我要把流量改成x到y  那么我要先在x的流量里面减去 y流向x的  再将剩下的流量 由x流向y  这样就可以视作y变成了树根 

我们用 F[y]表示 将根从x变成y后y流量的结果

F[y]=D[y]+\binom{min(F[x]-min(D[y],c[x][y]),c[x][y])\rightarrow d[x]>1}{c[x][y]\rightarrow d[x]=1}

然后当y深度遍历完后 在将流量复原(这很类似深搜的时候 回溯的操作 实际也差不多)

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 2e5+10;
typedef long long ll;
int d[N],h[N],nex[N<<1],to[N<<1],cur;
ll edge[N<<1],D[N],ans;
void add_edge(int x,int y,ll z){
	to[++cur]=y;nex[cur]=h[x];edge[cur]=z;h[x]=cur;
}
void dfs(int u,int fath){
	for(int i = h[u]; i; i = nex[i]){
		int v = to[i];
		if(v!=fath){
			dfs(v,u);
			if(d[v]==1) D[u]+=edge[i];
			else D[u]+=min(edge[i],D[v]); 
		}
	}
}
void dfs2(int u,int fath){
	ans=max(ans,D[u]);
	for(int i = h[u]; i; i = nex[i]){
		int v = to[i];
		if(v!=fath){
			if(d[u]==1) D[v]+=edge[i];
			else D[v]+=min(D[u]-min(D[v],edge[i]),edge[i]);
			dfs2(v,u);
			if(d[u]==1) D[v]-=edge[i];
			else D[v]-=min(D[u]-min(D[v],edge[i]),edge[i]);
		}
	}
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		cur=0;
		memset(h,0,sizeof(h));
		memset(D,0,sizeof(D));
		memset(d,0,sizeof(d));
		int n;
		scanf("%d",&n);
		for(int i = 1; i <= n-1; i++){
			int u,v;ll w;
			scanf("%d%d%lld",&u,&v,&w);
			add_edge(u,v,w);add_edge(v,u,w);
			d[v]++,d[u]++;
		}
		dfs(1,0);
		ans = 0;
		dfs2(1,0);
		printf("%lld\n",ans);
	}
	return 0;
} 
原创文章 85 获赞 103 访问量 2503

猜你喜欢

转载自blog.csdn.net/weixin_43824564/article/details/105554828