[COCI 2015] kamp 聚会

题目大意:n个点的一棵树上有k个关键点,每条边有一个距离,要求计算从每个点出发,用一条路径按任意顺序分别访问这k个点的路径总长度的最小值。(k<=n<=500000)

让我想艹的细节题。。。。一开始以为是sdoi2015的寻宝游戏,但我不会写那题。。。。。。
后来自已yy了一个O(n)的树形dp,但是细节TMD的多。

首先从1号点跑一遍dp。
用f1[i]表示i号节点的子树的访问关键点的最小代价,g1[i]表示访问完关键点还要回到第i号点的最小代价。然后把那些子树中不包含关键节点的儿子砍掉,可以得出

这里写图片描述

其中dis表示i号节点到他所有有用孩子的距离之和,w[i][son]表示i号点到这个孩子的距离。(即从i号点向下走遍所有有用节点再回来,然后选择一个代价最小的停下)
用_max1[i]保存一下那个f1[i]上取max的那个东西,_max2[i]存一下次大值。

然后再跑一遍dp,计算出答案。
用f2[i]表示i号节点的父亲除了i这个孩子以外的答案,g2[i]表示它的父亲除它之外的走完所有关键点再回到它父亲的最小值,f3[i]表示i号节点的最终答案,g3[i]表示最终答案还要再走回来的值。转移类比一下上面那个,什么减一减,是它最大就选次大之类的(细节太多就不写了,全写出来的一大长串看着也烦 总之就是把它父亲当做它的儿子来做就对了。。。。)

(我语死早求轻喷)

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <deque>
#include <queue>
#include <set>
#include <map>
#include <ctime>
using namespace std;

typedef long long LL;
const int MAXN=500005; 
const LL INF=LL(1)<<LL(60);
int n,m,tot,pos[MAXN],fst[MAXN],pre[MAXN*2],to[MAXN*2],cost[MAXN*2];
LL f1[MAXN],f2[MAXN],g1[MAXN],g2[MAXN],f3[MAXN],g3[MAXN],_max1[MAXN],_max2[MAXN],fa[MAXN],fadis[MAXN]; 
bool vis[MAXN],flag[MAXN]; 

int Get()
{
    char ch; int v=0; bool f=false;
    while (!isdigit(ch=getchar())) if (ch=='-') f=true; v=ch-48;
    while (isdigit(ch=getchar())) v=v*10+ch-48;
    if (f) return -v;else return v; 
}

void add(int x,int y,int c)
{
    pre[++tot]=fst[x],fst[x]=tot,to[tot]=y,cost[tot]=c;
    pre[++tot]=fst[y],fst[y]=tot,to[tot]=x,cost[tot]=c;     
}

LL gmax(LL a,LL b) { if (a>b) return a; return b; }
void dfs1(int x)
{   
    vis[x]=true; _max1[x]=_max2[x]=g1[x]=f1[x]=0; LL dis=0;
    for (int i=fst[x];i;i=pre[i])
      { 
        int y=to[i];
        if (!vis[y])
          { 
            fa[y]=x,fadis[y]=LL(cost[i]); dfs1(y); LL tmp=0;  
            if (flag[y]) 
              {
                flag[x]=true,g1[x]+=g1[y],dis+=LL(cost[i]); 
                tmp=g1[y]-f1[y]+cost[i];
                if (tmp>_max1[x]) _max2[x]=_max1[x],_max1[x]=tmp;
                else if (tmp>_max2[x]) _max2[x]=tmp;
              }
          }
      }
    if (!flag[x]) g1[x]=f1[x]=0;
    else g1[x]+=(dis<<1),f1[x]=g1[x]-_max1[x];
}

void dfs2(int x)
{   
    vis[x]=true; g2[x]=f2[x]=0; LL dis=0; int son1=0,son2=0; 
    for (int i=fst[x];i;i=pre[i])
      { 
        int y=to[i];
        if (!vis[y] && flag[y]) { if (!son1) son1=y;else son2=y; }
      }
    if (x!=1)
      {
        LL tmp=g2[fa[x]]-f2[fa[x]]+fadis[fa[x]];
        if (flag[x]) 
          { 
            g2[x]=g3[fa[x]]-g1[x]-(fadis[x]<<1);
            if (_max1[fa[x]]==g1[x]-f1[x]+fadis[x]) f2[x]=g2[x]-gmax(_max2[fa[x]],tmp);
            else f2[x]=g2[x]-gmax(_max1[fa[x]],tmp);
          }
        else g2[x]=g3[fa[x]],f2[x]=f3[fa[x]]; 
        g3[x]=g2[x]+g1[x]+(fadis[x]<<1),f3[x]=g3[x]-gmax(_max1[x],g2[x]-f2[x]+fadis[x]);
      }
    else g2[x]=f2[x]=0,g3[x]=g1[x],f3[x]=f1[x]; 

    for (int i=fst[x];i;i=pre[i])
      { 
        int y=to[i];
        if (!vis[y]) dfs2(y);
      }
} 

int main()
{ 
    //freopen("kamp.in","r",stdin);
    //freopen("kamp.out","w",stdout);
    n=Get(),m=Get(); int x,y,c; tot=0;
    if (m==0) { puts("0"); return 0; }
    for (int i=1;i<=n-1;i++) x=Get(),y=Get(),c=Get(),add(x,y,c);

    for (int i=1;i<=m;i++) x=Get(),flag[x]=true; 
    fa[1]=0,fadis[1]=0; dfs1(1);

    for (int i=1;i<=n;i++) vis[i]=false; 
    dfs2(1);
    for (int i=1;i<=n;i++) printf("%lld\n",f3[i]);
    return 0; 
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值