HDU4008 Parent and son [树形DP]

  给定一棵树,求出当x为根时y的最小儿子和最小后代各是多少。

  一道中等的树形DP,首先以1为根进行DP可以求出每个节点的最小儿子和最小后代。只有当x为y的孩子时才需要讨论,否则直接输出前面DP的结果即可,这里画个图就可以看出来,下面只讨论x为y的孩子的情况。

  如果y的最小儿子是x的祖宗节点,那这时该最小儿子已经变成了y的父亲,在选取最小儿子时不能在考虑这个点,应该换选次小儿子,所以每个点的次小儿子也要在DP时预处理出来,另外,这时y的原父亲在新树中已经变成了y的儿子,所以在选取最小儿子时要考虑父节点。至于最小后代,如果y不是根节点,那么最小后代必然是根节点1了,如果y节点是根节点,就先找到最小后代在它那个儿子的子树中,如果最小后代和x在同一棵子树中,那么现在已经成为了跟节点的父亲,换而选取其它子树中的最小后代,否则就直接选取最小根节点。

  至于祖宗节点的判断,用进出时间戳就可以了,儿子节点的时间戳必然是被父节点夹在中间的。

#include <stdio.h>
#include <string.h>
#include <algorithm>
#define INF 0x3fffffff
#define MAXN 100005

struct edge{
    int v,n;
}e[MAXN<<1];
int first[MAXN],es;
int mins[MAXN][2],mind[MAXN],mind2,mindv,dfn[MAXN][2],fat[MAXN],ds;
void addedge(int u,int v){
    e[es].v=v,e[es].n=first[u],first[u]=es++;
}
inline int min(int x,int y){return x<y?x:y;}
inline void swap(int &x,int &y){x^=y,y=x^y,x=x^y;}
void dp(int u,int f){
    dfn[u][0]=++ds;
    for(int i=first[u];i!=-1;i=e[i].n){
        int v=e[i].v;
        if(v==f)continue;
        fat[v]=u;
        dp(v,u);
        //跟新最小和次小儿子
        if(v<mins[u][1])mins[u][1]=v;
        if(mins[u][1]<mins[u][0])swap(mins[u][0],mins[u][1]);
        //跟新最小后代
        int tmp=min(v,mind[v]);
        if(tmp<mind[u])mind[u]=tmp;
    }
    dfn[u][1]=++ds;
}
//判读u是不是v的father
int isfather(int u,int v){
    return dfn[u][0]<=dfn[v][0]&&dfn[v][1]<=dfn[u][1];
}
int cas,n,q,tu,tv;
int main(){
    //freopen("test.in","r",stdin);
    scanf("%d",&cas);
    while(cas--){
        scanf("%d%d",&n,&q);
        for(int i=1;i<=n;i++)first[i]=-1;es=0;
        for(int i=0;i<n-1;i++){
            scanf("%d%d",&tu,&tv);
            addedge(tu,tv);
            addedge(tv,tu);
        }
        for(int i=1;i<=n;i++)mins[i][0]=mins[i][1]=INF;
        for(int i=1;i<=n;i++)mind[i]=INF;
        ds=0,fat[1]=INF;
        dp(1,-1);
        //跟新根节点次大的孩子,同时记录最小后代与1的最近节点
        mind2=INF;
        for(int i=first[1];i!=-1;i=e[i].n){
            int v=e[i].v,tmp=min(v,mind[v]);
            if(tmp!=mind[1]&&tmp<mind2)mind2=tmp;
            if(tmp==mind[1])mindv=v;
        }
        while(q--){
            scanf("%d%d",&tu,&tv);
            //如果这个点只连了一条边,说明无孩子
            if(e[first[tv]].n==-1){
                printf("no answers!\n");
            //如果tv是tu的父亲(以1为根)
            }else if(isfather(tv,tu)){
                int mis,mid;
                //如果最小的儿子是tu的祖宗节点,则此时已经成为了tv的父亲节点,换为次小的儿子
                //同时原来的父亲节点已经变成了孩子,要一起考虑
                if(isfather(mins[tv][0],tu))mis=min(mins[tv][1],fat[tv]);
                else mis=min(mins[tv][0],fat[tv]);
                //如果tv是根节点要特判,选取他最大或者次大的后代,否则最小后代就是1
                if(tv==1)mid=isfather(mindv,tu)?mind2:mind[1];
                else mid=1;
                printf("%d %d\n",mis,mid);
            //其它情况
            }else{
                printf("%d %d\n",mins[tv][0],mind[tv]);
            }
        }
        printf("\n");
    }
    return 0;
}

转载于:https://www.cnblogs.com/swm8023/archive/2012/08/27/2659248.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值