• 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;
    }
  • 相关阅读:
    mysql-数据库增删改查
    判断,循环
    数组
    html 三种垂直居中
    箭头函数
    Array类型
    object
    JAVA WEB 行业技术
    一个好的程序员
    经典语录
  • 原文地址:https://www.cnblogs.com/swm8023/p/2659248.html
Copyright © 2020-2023  润新知