[Bzoj3743][Coci2015] Kamp【换根Dp】

Online JudgeBzoj3743

Label:换根Dp,维护最长/次长链

题目描述

一颗树n个点,n-1条边,经过每条边都要花费一定的时间,任意两个点都是联通的。

有K个人(分布在K个不同的点)要集中到一个点举行聚会。

聚会结束后需要一辆车从举行聚会的这点出发,把这K个人分别送回去。

请你回答,对于i=1~n,如果在第i个点举行聚会,司机最少需要多少时间把K个人都送回家。

输入

第一行两个数,n,K。

接下来n-1行,每行三个数,x,y,z表示x到y之间有一条需要花费z时间的边。

接下来K行,每行一个数,表示K个人的分布。

输出

输出n个数,第i行的数表示:如果在第i个点举行聚会,司机需要的最少时间。

样例

Input

5 3
0
1
0
1 4
2 5
4 5
3 5

Output

2

题解

对于根节点做聚餐点的情况很容易得出。

\(ans[root]=2*∑^{K}_{i=1}dep[person[i]]-max(dep[person[1..K]])\)

也就是,所有住处的深度之和*2(要往返到其他住处)\(-\)住处的最大深度(可以选择最后去深度最大的那个点,这样它就不用了计两次了)。这样求根节点做聚餐点的情况只用dfs一遍,找一个住处的最大深度即可。


求其他非根节点做聚餐点的情况。

对于它子树内的情况和上面一个求法,但对于子树外很明显要用到换根Dp​。维护一个最长/次长链即可,具体细节见代码。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=500100;
inline int read(){
    int x=0;char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x;
}
struct edge{
    int to,nxt,d;
}e[2*N];
int head[N],Ecnt,n,m;
inline void link(int u,int v,int d){
    e[++Ecnt].to=v,e[Ecnt].d=d,e[Ecnt].nxt=head[u];
    head[u]=Ecnt;
}
int cnt[N],son[N],ma1[N],ma2[N],len[N];
/*
cnt[]:子树中住处的个数
ma1/2[]:最长/次长链长度(在第一遍dfs中只包含子树中的链,第二遍dfs中考虑了子树外的链)
son[]:i的最长链由i的哪个儿子son引上来(在第二遍dfs中,如果最长链发生改变,则将son赋值为0,表示在子树外)
len[]:住处的深度总和(第二遍dfs时算上子树外住处离自己的距离总和)

*/
bool mark[N];
void dfs(int x,int fa){ //第一遍,处理子树
    if(mark[x])cnt[x]=1;
    for(register int i=head[x];i;i=e[i].nxt){
        int y=e[i].to;if(y==fa)continue;
        dfs(y,x);
        cnt[x]+=cnt[y],len[x]+=len[y];
        if(!cnt[y])continue;
        len[x]+=e[i].d;
        int nowLink=ma1[y]+e[i].d;
        if(nowLink>=ma1[x]){
            ma2[x]=ma1[x],ma1[x]=nowLink,
            son[x]=y;
        }
        else if(nowLink>=ma2[x])ma2[x]=nowLink;
    }
}
void redfs(int x,int fa){//第二遍,换根。处理子树外
    for(register int i=head[x];i;i=e[i].nxt){
        int y=e[i].to;if(y==fa)continue;
		//将以x为根的所有状态转移到以y为根 
        len[y]=len[x]; 
        if(cnt[y]==0)len[y]+=e[i].d;//y离所有标记点远了
        if(cnt[y]==m)len[y]-=e[i].d;//y离所有标记点近了	
        int nowLink=(y==son[x]?ma2[x]:ma1[x])+e[i].d;
        if(cnt[y]!=m){//!!!注意
            if(nowLink>=ma1[y]){
                ma2[y]=ma1[y],ma1[y]=nowLink;
                son[y]=0;//y此时的最长链在它的子树外 
            }
            else if(nowLink>=ma2[y])ma2[y]=nowLink;
        }
        redfs(y,x);
    }
}
signed main(){
    n=read(),m=read();
    for(register int i=1;i<n;i++){
        int u=read(),v=read(),d=read(); 
        link(u,v,d),link(v,u,d);
    }
    for(register int i=1;i<=m;i++)mark[read()]=1;
    dfs(1,0);
    redfs(1,0);
    for(register int i=1;i<=n;i++)printf("%lld\n",2*len[i]-ma1[i]);
    return 0;
}

update:

一开始下面这个操作没有判(\(cnt[y]!=m\))但在bzoj上也A了,后来做到了重题[COCI2014/2015 Contest#1 F]发现会被下面这个数据搞掉。

if(cnt[y]!=m){//!!!注意
   if(nowLink>=ma1[y]){
   		ma2[y]=ma1[y],ma1[y]=nowLink;
   		son[y]=0;//y此时的最长链在它的子树外 
	}
 	else if(nowLink>=ma2[y])ma2[y]=nowLink;
}
/*
输入:
5 2
2 5 1
2 4 1
1 2 2
1 3 2
4 
5
正确输出:
5
3
7
2
2
*/
posted @ 2019-09-15 15:40  real_lyb  阅读(276)  评论(0编辑  收藏  举报