题目链接:https://cn.vjudge.net/contest/209473#problem/D
题目大意:有一棵树,这棵树的前n个节点拥有子节点,告诉你n的大小,以及这n个节点各有的子节点个数,对于Q个询问<x,y>,回答x是否是y的祖先。
解题思路:
一开始想的LCA,倍增解决。然后一看数据范围,2e7!瞬间被吓回来了。
想了各种方法百思不得其解最后看了题解orz(强烈建议独立思考,挫败感很强!)。
题解……万分机智,我们思考一下深搜建树的过程,先遍历到一个父亲节点,再往下遍历到这个父亲节点的子节点,再往上回溯到该父亲节点。我们发现如果A是B的祖先,A的遍历时间点早于B的遍历时间点早于B的回溯时间点早于A的回溯时间点。
也就是说我们如果给每个点建立两个时间值(类似Tarjan的思想,预计后天讲Tarjan),第一个时间值是遍历时间点,第二个时间值是回溯时间点,建树的时候预处理出来,那么对于每个询问,就可以O(1)得出结果了(判断一下四个值大小关系)。
这样乍一听好像很好实现,然而此题还有一个trick,就是什么样的电脑才能深搜递归2e7层?果断会爆栈!
最后这道题的难点(?)转化成了手动扩栈模拟dfs,很简单的样子,就不赘述了。
坑点:注意数组大小,容易RE(别开太大了,稍微大一点就好)。
下面放965msAC代码:
1 /* by Lstg */ 2 /* 2018-03-05 21:02:03 */ 3 4 #include<stdio.h> 5 #include<iostream> 6 #include<queue> 7 #define MAXN 20000005 8 using namespace std; 9 10 int dfn[MAXN],dfm[MAXN],stk[MAXN],n; 11 queue<int>g[300005];//这里太大就会RE 12 13 void _mydfs(int x){ 14 15 int top=0,num=1,y; 16 dfn[x]=num++; 17 stk[++top]=x; 18 while(top){ 19 x=stk[top]; 20 if(x>n||g[x].empty()){/*先判断x的大小防RE和WA(有时溢出不会告诉你是RE,而会返回WA)*/ 21 dfm[x]=num++; 22 top--; 23 } 24 else{ 25 y=g[x].front(); 26 g[x].pop(); 27 dfn[y]=num++; 28 stk[++top]=y; 29 } 30 } 31 } 32 33 int main(){ 34 35 int T,tot,i,x,y; 36 scanf("%d",&T); 37 for(int cc=1;cc<=T;cc++){ 38 scanf("%d",&n); 39 tot=0; 40 41 for(i=0;i<n;i++){ 42 while(!g[i].empty())g[i].pop(); 43 scanf("%d",&x); 44 while(x--) 45 g[i].push(++tot); 46 } 47 _mydfs(0); 48 printf("Case %d: ",cc); 49 scanf("%d",&n); 50 for(i=1;i<=n;i++){ 51 scanf("%d%d",&x,&y); 52 if(dfn[x]<dfn[y]&&dfm[x]>dfm[y]) 53 puts("Yes"); 54 else puts("No"); 55 } 56 if(cc!=T)putchar(10); 57 } 58 return 0; 59 }