[COCI2014-2015#1] Kamp

题目

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

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

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

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

输入格式
第一行两个整数 n,Kn,K 。

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

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

输出格式
输出 nn 个数。

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

输入输出样例
输入 #1复制
7 2
1 2 4
1 3 1
2 5 1
2 4 2
4 7 3
4 6 2
3
7
输出 #1复制
11
15
10
13
16
15
10
说明/提示
数据规模与约定
对于 50%50% 的数据,保证 n\le 2\times 10^3n≤2×10
3

对于 100%100% 的数据, 1 \le k \le n \leq 5\times 10^51≤k≤n≤5×10
5
,1 \le x,y \le n1≤x,y≤n,1 \le z \le 10^81≤z≤10
8

思路

x x x点出发,答案就是 x x x和所有目标形成的虚树大小 × 2 − x \times2-x ×2x到最远的目标的距离
那么换根 d p dp dp求出每个点的虚树大小(大概第一遍是 d p x dp_x dpx表示从 x x x x x x子树里的目标点的叙述大小,第二遍考虑换根的影响就好了)。
第二个求最远距离,那么考虑如何换根,第一种是从 x x x 走到儿子 s s s s s s不是 x x x的最远目标点,那么最远点一定是从原来最远点走过去的。第二种是从 x x x走到最远点,那么答案还有可能是从 s s s外转移得到的,具体就是从 x x x外转移和从其他儿子转移,那么维护一个子树内最大值,和子树外最大值就好了。子树外最大值可以从当前点父亲和当前点父亲的其他儿子中最大的转移(就是子树内次大值)。

代码

#include<bits/stdc++.h>
#define N 500010
#define int long long
using namespace std;
int n,K;
struct Node{int to,next,val;}e[N<<1];
int ls[N],cnt;
void add(int u,int v,int w){e[++cnt]={v,ls[u],w};ls[u]=cnt;}
int pos[N];
int sz[N],g[N],f[N];
int len[N],id[N],slen[N];
void dfs(int u,int fa)
{
    if(pos[u])sz[u]=1;
    for(int i=ls[u];i;i=e[i].next)
    {
        int v=e[i].to,w=e[i].val;
        if(v==fa)continue;
        dfs(v,u);
        if(sz[v])
        {
            g[u]+=g[v]+2*w;
            int now=len[v]+w;
            if(now>=len[u])slen[u]=len[u],len[u]=now,id[u]=v;
            else if(now>slen[u])slen[u]=now;
        }
        sz[u]+=sz[v];
    }
}
void dp(int u,int fa)
{
    for(int i=ls[u];i;i=e[i].next)
    {
        int v=e[i].to,w=e[i].val;
        if(v==fa)continue;
        if(!sz[v])f[v]=f[u]+2*w,len[v]=len[u]+w;
        else if(K-sz[v])
        {
            f[v]=f[u];
            if(id[u]!=v&&len[v]<len[u]+w)
                slen[v]=len[v],len[v]=len[u]+w,id[v]=u;
            else if(len[v]<slen[u]+w)
                slen[v]=len[v],len[v]=slen[u]+w,id[v]=1;
            else if(slen[v]<len[u]+w&&id[u]!=v)
                slen[v]=len[u]+w;
            else if(slen[v]<slen[u]+w)
                slen[v]=slen[u]+w;
        }
        else f[v]=g[v];
        dp(v,u);
    }
}
signed main()
{
    scanf("%lld%lld",&n,&K);
    for(int i=1;i<n;i++)
    {
        int u,v,w;scanf("%lld%lld%lld",&u,&v,&w);
        add(u,v,w);add(v,u,w);
    }
    for(int i=1;i<=K;i++){int x;scanf("%lld",&x);pos[x]=1;}
    dfs(1,0);f[1]=g[1];dp(1,0);
    for(int i=1;i<=n;i++)printf("%lld\n",f[i]-len[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值