287. 积蓄程度

题目

有一个树形的水系,由 N-1 条河道和 N 个交叉点组成。
我们可以把交叉点看作树中的节点,编号为 1~N,河道则看作树中的无向边。
每条河道都有一个容量,连接 x 与 y 的河道的容量记为 c(x,y)。
河道中单位时间流过的水量不能超过河道的容量。
有一个节点是整个水系的发源地,可以源源不断地流出水,我们称之为源点。
除了源点之外,树中所有度数为 1 的节点都是入海口,可以吸收无限多的水,我们称之为汇点。
也就是说,水系中的水从源点出发,沿着每条河道,最终流向各个汇点。
在整个水系稳定时,每条河道中的水都以单位时间固定的水量流向固定的方向。
除源点和汇点之外,其余各点不贮存水,也就是流入该点的河道水量之和等于从该点流出的河道水量之和。
整个水系的流量就定义为源点单位时间发出的水量。
在流量不超过河道容量的前提下,求哪个点作为源点时,整个水系的流量最大,输出这个最大值。

输入格式
输入第一行包含整数T,表示共有T组测试数据。
每组测试数据,第一行包含整数N。
接下来N-1行,每行包含三个整数x,y,z,表示x,y之间存在河道,且河道容量为z。
节点编号从1开始。

输出格式
每组数据输出一个结果,每个结果占一行。
数据保证结果不超过231−1。

数据范围
N≤2∗105
输入样例:
1
5
1 2 11
1 4 13
3 4 5
4 5 10

输出样例:
26

拿到这题一个直接的想法就是枚举每一个点作为根节点。
之后就是树形dp
因为一个节点能通过最大流量取决于下面的子节点和父节点与它连接的河道,需要先计算出子节点才好计算父节点。
因为这个原因,用树形dp就很棒。
设d[x]是点x能流下去的最大流量。
那么
对于一条边(x,v)
如果v的度为1
d[x]+=w(x,u);
如果v的度不为1
d[x]+=min(d[v],w(x,v));

void dfs(int x){
	book[x]=1;
	d[x]=0;
	for(int i=head[x];i;i=next[i]){
		int v=ver[i],w=wer[i];
		dfs(v);
		if(du[v]==1) d[x]+=w;
		else d[x]+=min(d[v],w);
	}
}

虽然思路是对的,但是会超时。
现在介绍一种二次扫描法
设f[x]是以x为源点的流域最大流量
扫描了一遍后
求出了在以某节点为根的情况下的d
现在有一张以1为根的树
以1为很的树
显然有f[1]=d[1];
对于其他点就要把问题拆成两份
如图
假设要计算f[2]
在这里插入图片描述
假装2是根,
f[2]=d[2]+min(w(1,2),d[x] (在2是根节点情况下的d[1]));
此情况下的d[x]=f[x]-min(w(1,2),d[2] (以1为根节点情况下的d[2]));

void dp(int x){
	book[x]=1;
	for(int i=head[x];i;i=nex[i]){
		int v=ver[i],w=wer[i];
		if(book[v]) continue;
		if(du[x]==1) f[v]=d[v]+w;
		else f[v]=d[v]+min(f[x]-min(w,d[v]),w);
		dp(v);
	}
}
#include<bits/stdc++.h>
using namespace std;
const int ll=4e5+5;
int t,n,tot;
int head[ll],nex[ll],ver[ll],wer[ll],du[ll],d[ll],f[ll],book[ll];
void add(int u,int v,int w){
	tot++;
	du[v]++;
	ver[tot]=v;
	wer[tot]=w;
	nex[tot]=head[u];
	head[u]=tot;
}
void dfs(int x){
	book[x]=1;
	d[x]=0;
	for(int i=head[x];i;i=nex[i]){
		int v=ver[i],w=wer[i];
		if(book[v]) continue;
		dfs(v);
		if(du[v]==1) d[x]+=w;
		else d[x]+=min(w,d[v]);
	}
}
void dp(int x){
	book[x]=1;
	for(int i=head[x];i;i=nex[i]){
		int v=ver[i],w=wer[i];
		if(book[v]) continue;
		if(du[x]==1) f[v]=d[v]+w;
		else f[v]=d[v]+min(f[x]-min(w,d[v]),w);
		dp(v);
	}
}
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		memset(du,0,sizeof(du));
		memset(f,0,sizeof(f));
		memset(d,0,sizeof(d));
		memset(book,0,sizeof(book));
		tot=0;
		for(int i=1;i<=n-1;i++){
			int u,v,w;
			scanf("%d%d%d",&u,&v,&w);
			add(u,v,w);
			add(v,u,w);
		}
		int root=1;
		dfs(root);
		memset(book,0,sizeof(book));
		f[root]=d[root];
		dp(root);
		int ans=0;
		for(int i=1;i<=n;i++)
			ans=max(ans,f[i]);
		printf("%d\n",ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值