给定一棵树,求出当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; }