2019.9.16NOIP模拟赛B、泳池

问题 B: 泳池

时限: 1 Sec  内存: 256 MB

题目描述

小 A 的城市里有 n 座工厂,编号分别为 1 ∼ n。工厂间连有 n − 1 条双向管道,形成一个无向 连通图,其中每条管道都有一定的长度,连接在两座不同的工厂间。 每座工厂都装有废水处理设施,工厂 i 的蓄水量记为 ci。由于工厂规模有限,工厂产生的废水 必须经由管道输送到另一座工厂进行处理。 工厂 u 将废水输送到工厂 v 处理时,所需的运输成本等于无向图中 u, v 间最短路径的长度,并 且会产生 cu − cv 的额外成本(可能为负)。总成本等于运输成本与额外成本的和。 为了降低污染,在接下来的 q 天内,每一天只有一座工厂会产生废水。你需要确定这座工厂将 废水输送到哪一座工厂进行处理,可使得总成本最小。由于选择可能不唯一,你只需输出最小的总 成本。

输入

第一行一个正整数 n。 
第二行 n 个正整数 ci。 下接 n − 1 行,每行三个正整数 u, v,w,表示一条双向管道两端工厂的编号及长度。 
第 n + 2 行一个正整数 q。 下接 q 行,每行一个正整数 x,表示这一天进行生产的工厂的编号。 

输出

输出 q 行,每行一个整数,表示这一天总成本的最小值。

样例输入

5
7 7 6 9 9
2 5 5
2 3 1
4 1 1
1 2 2
4
2
5
3
4

样例输出

1
7
0
3

提示

第 1 天,工厂 2 输送到工厂 4 是一种最优方案,成本为 3 + (−2) = 1。 第 2 天,工厂 5 输送到工厂 2 是一种最优方案,成本为 5 + 2 = 7。 第 3 天,工厂 3 输送到工厂 2 是一种最优方案,成本为 1 + (−1) = 0。 第 4 天,工厂 4 输送到工厂 1 是一种最优方案,成本为 1 + 2 = 3。

 

 

 

题解

树型DP

设f[u]为点u在其子树内的最优选择,g[u]为点u在其子树外的最优选择

那么我们可以很轻松地算出f[u],顺便记录一下它是由哪一个儿子转移过来的

接下来考虑怎么算g[u]

首先可以发现一个性质(不妨设点v的父亲是u):

当u的最优选择不在v的子树中时,则v的子树外的最优选择一定是u在全局的最优选择(有可能是u本身)

当u的最优选择在v的子树中时,则v的子树外的最优选择一定是u在全局的次优选择(也有可能是u本身),即除去子树v后的u最优选择,对于这种情况,我们可以分一下类:

(1)在v的兄弟的子树中(不包含兄弟本身):f[brother]

(2)在v的兄弟中:brother

(3)是它的父亲u本身:u

(4)在它父亲的子树外:g[fa]

然后就可以从上向下计算了

 

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gi()
{
	char c;int num=0,flg=1;
	while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
	while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
	return num*flg;
}
#define N 200005
int fir[N],to[2*N],nxt[2*N],cd[2*N],cnt;
void adde(int a,int b,int c)
{
	to[++cnt]=b;cd[cnt]=c;nxt[cnt]=fir[a];fir[a]=cnt;
	to[++cnt]=a;cd[cnt]=c;nxt[cnt]=fir[b];fir[b]=cnt;
}
int a[N];
int f[N],g[N],son[N],stt[N],fa[N];
void dfs1(int u)
{
	//f[u]=dis(k,u)+a[u]-a[k];
	f[u]=0x3f3f3f3f;
	int v,p;
	for(p=fir[u];p;p=nxt[p]){
		v=to[p];
		if(v!=fa[u]){
			fa[v]=u;
			dfs1(v);
			if(f[u]>f[v]-a[v]+a[u]+cd[p]){
				f[u]=f[v]-a[v]+a[u]+cd[p];
				son[u]=son[v];stt[u]=v;
			}
			if(f[u]>cd[p]+a[u]-a[v]){
				f[u]=cd[p]+a[u]-a[v];
				son[u]=v;stt[u]=v;
			}
		}
	}
}
void dfs2(int u)
{
	if(u==1) g[u]=0x3f3f3f3f;
	int v,p,tmp=0x3f3f3f3f;
	for(p=fir[u];p;p=nxt[p]){
		v=to[p];
		if(v!=fa[u]&&v!=stt[u]){
			g[v]=cd[p]+a[v]-a[u];//qson->fa
			g[v]=min(g[v],min(f[u],g[u])-a[u]+a[v]+cd[p]);//qson->f[fa] and qson->g[fa]
			tmp=min(tmp,min(f[v]-a[v]+cd[p],cd[p]-a[v]));//record brother and f[brother]
		}
	}
	for(p=fir[u];p;p=nxt[p]){
		v=to[p];
		if(v!=fa[u]&&v==stt[u]){
			g[v]=cd[p]+a[v]-a[u];//zson->fa
			g[v]=min(g[v],tmp+cd[p]+a[v]);//zson->brother
			g[v]=min(g[v],g[u]-a[u]+a[v]+cd[p]);//zson->g[fa]
			break;
		}
	}
	for(p=fir[u];p;p=nxt[p])
		if(to[p]!=fa[u])
			dfs2(to[p]);
}
int main()
{
	int n,Q,i,u,v,w,x;
	n=gi();
	for(i=1;i<=n;i++)
		a[i]=gi();
	for(i=1;i<n;i++){
		u=gi();v=gi();w=gi();
		adde(u,v,w);
	}
	dfs1(1);
	dfs2(1);
	Q=gi();
	for(i=1;i<=Q;i++){
		x=gi();
		printf("%d\n",min(f[x],g[x]));
	}
}

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值