P1131 [ZJOI2007]时态同步 树形DP

题目描述
小Q在电子工艺实习课上学习焊接电路板。一块电路板由若干个元件组成,我
们不妨称之为节点,并将其用数字1,2,3….进行标号。电路板的各个节点由若
干不相交的导线相连接,且对于电路板的任何两个节点,都存在且仅存在一条
通路(通路指连接两个元件的导线序列)。

在电路板上存在一个特殊的元件称为“激发器”。当激发器工作后,产生一个激
励电流,通过导线传向每一个它所连接的节点。而中间节点接收到激励电流
后,得到信息,并将该激励电流传向与它连接并且尚未接收到激励电流的节
点。最终,激烈电流将到达一些“终止节点”――接收激励电流之后不再转发的
节点。

激励电流在导线上的传播是需要花费时间的,对于每条边e,激励电流通过它需
要的时间为t_e ,而节点接收到激励电流后的转发可以认为是在瞬间完成的。
现在这块电路板要求每一个“终止节点”同时得到激励电路――即保持时态同
步。由于当前的构造并不符合时态同步的要求,故需要通过改变连接线的构
造。目前小Q有一个道具,使用一次该道具,可以使得激励电流通过某条连
接导线的时间增加一个单位。请问小Q最少使用多少次道具才可使得所有的
“终止节点”时态同步?
输入格式
第一行包含一个正整数N,表示电路板中节点的个数。
第二行包含一个整数S,为该电路板的激发器的编号。
接下来N−1行,每行三个整数a,b,t。表示该条导线连接节点a与节点b,且激励
电流通过这条导线需要t个单位时间。

输出格式
仅包含一个整数V,为小Q最少使用的道具次数。

输入输出样例
输入 #1
3
1
1 2 1
1 3 3
输出 #1
2
说明/提示
对于40%的数据,N ≤ 1000N≤1000
对于100%的数据,N ≤ 500000N≤500000
对于所有的数据,t_e≤1000000

定义f[i]表示以i为父节点的子树时态同步需要的最小花费,t[i]为以i为父节点的子树同步的那个时态(不考虑i的父节点)。

其实t[i]就是i到离i最远的叶子结点的距离,该叶子结点距离i最远它被激发后其他节点也一定被激发了,这个时间也一定是最短的,所以就要把其他所有该子树中节点都调到这一时刻被激发就同步了。边距离用距离根节点远的那个端点的距离代替,要使操作数少就要优先对距离根节点近的边操作,因为这个边加1之后它字面的所有边都相当于加1了,所以先对距离近的边操作,再对距离远的边操作,并且确保每个点的t[]不能超过根节点到最远叶子结点的距离。(以上都是指的子树,并不是整棵树)

其实可以逆向思考,根节点先被激发,所有叶子结点同时被激发,那么就可以反过来考虑,所有叶子结点先被同时激发,电流同时到达根节点,那么叶子结点的t[]就是0,然后用叶子结点更新它的父节点,父节点的t[]就是离他最远的子节点的距离(该距离记为res),那么就要使其他子节点与最远的子节点的同步了,每条边就要操作res-w[]次,这几条边相加就是父节点f[]了,t[]就是res,这个时候的这棵子树已经时态同步了,然后继续向上递归回到它的父节点,可以发现这个时候根节点的所有子节点的子树内已经时态同步,子树与子树之间确并不同步,只需要对这几个子节点操作就好了(以子节点为跟的子树已经同步,对子节点操作只会使同步的时间变化,却不会不会破坏同步)就对子节点一样的操作就好了,一直向上递归直到到达根节点s。

#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=1000010;
int n,s;
ll f[N],t[N];
int h[N],e[N],w[N],ne[N],idx;
void add(int a,int b,int c)
{
	e[idx]=b;
	w[idx]=c;
	ne[idx]=h[a];
	h[a]=idx++;
}

void DP(int u,int pre)
{
	ll res=0,cnt=0,sum=0,num=0;//res最大的距离,cnt所有子节点的操作和,sum所有边的距离和,num边的数量
	for(int i=h[u];i!=-1;i=ne[i])
		if(e[i]!=pre)
		{
			num++;
			int v=e[i];
			DP(v,u);
			sum+=(w[i]+t[v]);
			res=max(w[i]+t[v],res);
			cnt+=f[v]; 
		}
	t[u]=res;
	f[u]=res*num-sum+cnt;
	return ;
}
int main()
{
	memset(h,-1,sizeof h);
	scanf("%d%d",&n,&s);
	for(int i=1;i<n;i++)
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
		add(b,a,c);
	}
	DP(s,-1);
	cout<<f[s];
	return 0;
}
原文地址:https://www.cnblogs.com/neflibata/p/12871763.html