【BZOJ】 [Coci2015]Kamp-树形DP

传送门:BZOJ3743Kamp


题意

     一颗树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个点举行聚会,司机需要的最少时间。


数据规模

K <= N <= 500000
1 <= x,y <= N, 1 <= z <= 1000000


题解

    我们任意以某个人的位置结点设为树根dfs构造一颗树。
    将所有人的位置结点之间连接,构造出实树(就这样叫),在实树中的结点的答案就是两倍的总链的长度减去以它为根在实树范围内的最长链的长度
    同样对于不在实树范围内的结点,我们可以肯定它是某一个实树内结点为根的子树中的一个结点。所以对于实数范围外的点,答案就是它到最近实树的位置结点的距离加上这个位置结点的答案
    剩下的就是代码实现的问题了。
    第一遍dfs求出这颗实树,F[x]指的是在实树内的以结点x为根的子树的边权之和的两倍(由上述结论,我们预先处理成两倍),就是向下走完这颗实树。
    第二遍dfs求出dis[x]在整个树范围内的结点x向上走(对于实树内的x,用它的父节点y的dis加上它们之间的边权和(F[y]-F[x]),对于实树外的,则加上x与y之间的边权),就是向上走完这颗实树。
    第三遍dfs求出ch[x][0],ch[x][1]在实树范围内的x向下走完(实树外的结点是不可能往下走的)实树的边权之和,ch[x][0]存的是最大值,ch[x][1]存的是次大值(当x在实树中,我们要比较它往上还是往下走答案更小,但有可能它在父节点往下走的最大路径上,这样就无法求出父节点不经过x结点的最大路径来更新答案了,所以我们要存一个次大值,而且要用每个子结点来更新这个次大值,以保证结点x不同时在最大值和次大值的链上(好像不是很有道理))。
    第四遍dfs就算整棵树范围内的结点往上走完实树的边权之和,d[x]。
    这样处理完之后,我们就可以得到想要的值啦。
    对于实树内:
     ans[x]=F[x]+dis[x]max(d[x],ch[x][0]) a n s [ x ] = F [ x ] + d i s [ x ] − m a x ( d [ x ] , c h [ x ] [ 0 ] )
    对于实树外:
     ans[x]=f[x]+dis[x]d[i] a n s [ x ] = f [ x ] + d i s [ x ] − d [ i ]
    又因为实树外的结点ch[][]数组都为零。所以可以合并为对于整颗树上的结点x:
     ans[x]=F[x]+dis[x]max(d[x],ch[x][0]) a n s [ x ] = F [ x ] + d i s [ x ] − m a x ( d [ x ] , c h [ x ] [ 0 ] )
    是不是这样就完了?
    然而我去膜拜了一发Claris的题解,抛开这窒息的压行,我的膝盖献给了神犇的思路,还妙妙的将dfs变成bfs了。顺便在文末贴一发神犇的还原版代码,和题解。


代码

蒟蒻的代码:


#include
using namespace std;
typedef long long ll;
const int N=5e5+10;

int n,k,tot,st;
int head[N],to[N<<1],c[N<<1],nxt[N<<1];
int g[N],f[N];
ll F[N],ch[N][2],dis[N],abv[N],d[N];

inline int read() {
    int x=0,f=1; char c=getchar();
    while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
    while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    return x*f;
}

inline void lk(int u,int v,int w)
{
    to[++tot]=v;nxt[tot]=head[u];head[u]=tot;c[tot]=w;  
    to[++tot]=u;nxt[tot]=head[v];head[v]=tot;c[tot]=w;
}


inline void df(int x)
{
    for(int i=head[x];i;i=nxt[i]){
        int e=to[i];
        if(e!=f[x]){
            f[e]=x;
            df(e);
            if(g[e]){
                g[x]=1;
                F[x]+=F[e]+(c[i]<<1); 
            }
        }
    }
}

inline void cal(int x)
{
    for(int i=head[x];i;i=nxt[i]){
       int e=to[i];
       if(e!=f[x]){ 
         if(g[e]){
            dis[e]=dis[x]+F[x]-F[e];
         }else{
            dis[e]=dis[x]+F[x]-F[e]+(c[i]<<1);
         }
         cal(e);
       } 
    } 
}

inline void update(ll y,int x)
{
    if(y>ch[x][0]) {ch[x][1]=ch[x][0];ch[x][0]=y;}
    else if(y>ch[x][1]) ch[x][1]=y;
}

inline int dfs(int x)
{
    for(int i=head[x];i;i=nxt[i]){
        int e=to[i]; 
        if(e!=f[x]){
            dfs(e);
            if(g[e]) update(ch[e][0]+c[i],x);
        }
    }
}

inline void get(int x)
{
    for(int i=head[x];i;i=nxt[i]){
        int e=to[i];
        if(e!=f[x]){
            d[e]=max(d[e],d[x]+c[i]);
            if(ch[x][0]==ch[e][0]+c[i]){
                d[e]=max(d[e],ch[x][1]+c[i]);
            }else d[e]=max(d[e],ch[x][0]+c[i]);
            get(e);
        }
    }
}

int main(){
    n=read();k=read();
    for(int i=1;iint u=read(),v=read(),z=read();
        lk(u,v,z);
    }
    st=read();g[st]=1;
    for(int i=2;i<=k;i++){
        g[read()]=1;
    }
    df(st);cal(st);
    dfs(st);get(st);
    for(int i=1;i<=n;i++)
     printf("%lld\n",F[i]+dis[i]-max(ch[i][0],d[i]));
    return 0;
}

神犇的题解:
d[x][0]表示x点向下走且回到x点的最少代价

d[x][1]表示x点向下走但不回到x点的最少代价

d[x][2]表示x点向下走的最长路

d[x][3]表示x点向下走的次长路

u[x][0]表示x点向上走且回到x点的最少代价

u[x][1]表示x点向上走但不回到x点的最少代价

一遍树形DP即可

ans[i]=min(d[i][0]+u[i][1],d[i][1]+u[i][0])

神犇的代码:

#include
#define N 500010
typedef long long ll;
int n,k,i,j,x,y,z,g[N],nxt[N<<1],v[N<<1],w[N<<1],ed;
int f[N],dis[N],h,t,q[N],size[N],de[N];
ll d[N][4],u[N][2],tmp;
bool is[N];

inline void read(int&a)
{
  char c;
  while(!(((c=getchar())>='0')&&(c<='9')));
  a=c-'0';
  while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';
}

inline void add(int x,int y,int z)
{
   v[++ed]=y;w[ed]=z;nxt[ed]=g[x];g[x]=ed;

}
inline ll min(ll x,ll y)
{
   return x<y?x:y;
}
int main(){
  read(n),read(k);
  for(i=1;iread(x),read(y),read(z),add(x,y,z),add(y,x,z);
  while(k--)
     read(x),is[x]=1;
  q[h=t=1]=1;
  //below:dfs->bfs amazing! 
  while(h<=t)
    for(j=g[x=q[h++]];j;j=nxt[j])
       if(v[j]!=f[x])
         f[q[++t]=v[j]]=x,dis[v[j]]=w[j];
  //finish
  for(i=n;i;i--){//from the top of the stack 
    size[x=q[i]]=is[x];
      for(j=g[x];j;j=nxt[j])
        if(v[j]!=f[x]&&size[v[j]]){
         // n->1 so that size[v[j]] and d[v[j]][]have already be updated when being used
         size[x]+=size[v[j]];
         d[x][0]+=d[v[j]][0]+2LL*w[j];
         tmp=d[v[j]][1]-d[v[j]][0]-w[j];
         if(tmpx][2])
          d[x][3]=d[x][2],d[x][2]=tmp,de[x]=v[j];
        else d[x][3]=min(d[x][3],tmp);
    }
    d[x][1]=d[x][0]+d[x][2];
  }
  //de[x] direct to the son of node x which has the largest chain
  for(i=2;i<=n;i++)
    if(size[1]>size[x=q[i]]){
      u[x][0]=u[f[x]][0]+d[f[x]][0]-d[x][0];
      u[x][1]=u[f[x]][1]+d[f[x]][0];
      if(de[f[x]]==x)
         u[x][1]=min(u[x][1],u[f[x]][0]+d[f[x]][0]+d[f[x]][3]);
      else 
         u[x][1]=min(u[x][1],u[f[x]][0]+d[f[x]][1]);
      u[x][1]-=d[x][0]+dis[x];
      if(!size[x])
         u[x][0]+=2*dis[x],u[x][1]+=2*dis[x];
  }
  for(i=1;i<=n;i++)
     printf("%lld\n",min(d[i][0]+u[i][1],d[i][1]+u[i][0]));
  return 0;
}

orzzzz

你可能感兴趣的:(树形DP)