zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    oracle数据库sql比较日期
    JAVA中替换字符的方法replace和replaceAll 区别
    得到某个字段为空的所有记录
    使用jQuery操作元素属性
    JS控制元素可见(显示)与不可见(隐藏)
    当JS出现的Cannot read property 'XXX' of null错误
    判断两个对象不相等
    js将字符串转换为数字等类型
    EL表达式各种函数使用大全
    ajax返回值传给js全局变量
  • 原文地址:https://www.cnblogs.com/neflibata/p/12871763.html
Copyright © 2011-2022 走看看