CSU_1414 Query On a Tree BFS序+DFS时间戳进行预处理

2014 csu校赛 I 题,比赛的时候拿着他看了几个小时愣是没弄出好的方法,我们也想过统计出每个root的节点总数,然后减去离它d层的子节点的数目,即为答案。但是因为树的存储是无序的,所以每次为了找到子节点还是需要搜索,这肯定是承受不了的。

事实上,这个思路大体是对的,只是需要进行预处理,使得在找离它为d层的子节点能尽量节约时间,此外,如果找到了子节点挨个去减,也是很费时的,因为这些子节点必定处在同一层,他们的bfs序是连续的,故比较容易想到用前缀和来快速求得某一层的所有儿子的数目。

我们看这样的树

因此我们如果能把每个root的dfs时间戳的最小值和最大值给记录一下,再求出bfs序,则,对于某一层的所有节点,只要是时间戳在该root的最小值和最大值范围内,则必定是我们要求得孩子节点。

  1 #include <iostream>
  2 #include <cstdio>
  3 #include <cstring>
  4 #include <algorithm>
  5 #include <vector>
  6 #define N 100010
  7 using namespace std;
  8 vector <int> deep_num[N];
  9 int u[N],v[N],nt[N],ft[N],cnt,dfs_clock;
 10 int last[N][2],max_deep[N],tot[N],deep[N],sum[N];
 11 void add(int a,int b){
 12     u[cnt]=a;
 13     v[cnt]=b;
 14     nt[cnt]=ft[a];
 15     ft[a]=cnt++;
 16 }
 17 void init(int num)
 18 {
 19     cnt=0;
 20     dfs_clock=0;
 21     sum[0]=0;
 22 
 23     for (int i=0;i<=num;i++){
 24         ft[i]=-1;
 25         nt[i]=0;
 26         u[i]=v[i]=0;
 27         deep_num[i].clear();
 28         deep_num[i].push_back(0); //先给每个层预先加一个0,方便之后处理
 29     }
 30 }
 31 void dfs(int u,int d)
 32 {
 33     last[u][0]=++dfs_clock;
 34     deep_num[d].push_back(u);//这里直接把节点存入相应的层,省去了BFS,直接达到了计算BFS序的作用
 35     tot[u]=1;
 36     deep[u]=d;
 37     max_deep[u]=d;
 38     for (int w=ft[u];w>=0;w=nt[w]){
 39         int x=v[w];
 40         dfs(x,d+1);
 41         tot[u]+=tot[x];//计算root节点下的节点总数
 42         max_deep[u]=max(max_deep[u],max_deep[x]);//记录该root最大层有多深
 43     }
 44     last[u][1]=dfs_clock;
 45     if (deep_num[deep[u]].size()>1)
 46         sum[u]=sum[deep_num[deep[u]][deep_num[deep[u]].size()-2]]+tot[u];//因为没有进行BFS,所以计算前缀和要稍微麻烦一点,根据存贮好的每一层的节点来计算
 47     else
 48         sum[u]=sum[deep_num[deep[u]-1][deep_num[deep[u]-1].size()-1]]+tot[u];
 49 }
 50 int query(int x,int d)
 51 {
 52     if (max_deep[x]<=deep[x]+d-1)//如果该root没有那一层的孩子,则可以直接返回,因为肯定不需要减孩子了
 53         return tot[x];
 54     int val=last[x][0];
 55     int ret=tot[x];
 56     int nd=deep[x]+d;
 57     int L=0,R=deep_num[nd].size()-1,mid;
 58     int a,b;
 59     while (L<R)//
 60     {
 61 
 62         mid=(L+R)/2;
 63         //cout<<L<<" "<<R<<" "<<mid<<endl;
 64         int nx=deep_num[nd][mid];
 65         if (last[nx][0]>=val) R=mid;
 66         else L=mid+1;
 67         //cout<<nx<<" !!! "<<endl;
 68     }
 69     //cout<<"pass"<<endl;
 70     a=deep_num[nd][L-1];算出左边界,因为要算前缀和,所以L-1,这样前面的push 0在这里也起到作用了
 71     val=last[x][1];
 72     L=0,R=deep_num[nd].size()-1;
 73     while (L<R)
 74     {
 75         mid=R-(R-L)/2;
 76         int nx=deep_num[nd][mid];
 77         if (last[nx][1]<=val) L=mid;
 78         else R=mid-1;
 79        // cout<<nx<<" ~~ "<<endl;
 80     }
 81     b=deep_num[nd][R];
 82     //cout<<a<<" ab "<<b<<endl;
 83     //cout<<sum[a]<<" sum "<<sum[b]<<endl;
 84     ret-=sum[b]-sum[a]; //减去前缀和,其实还有个细节就是因为刚刚已经判断了,所以一定有孩子在这一层,所以这样减一定是合理的,不会出现没孩子也减掉的情况
 85     return ret;
 86 
 87 }
 88 int main()
 89 {
 90     int t,n,m,x,d;
 91     scanf("%d",&t);
 92     while (t--){
 93         scanf("%d",&n);
 94         init(n);
 95         for (int i=2;i<=n;i++){
 96             int tmp;
 97             scanf("%d",&tmp);
 98             add(tmp,i);
 99         }
100         dfs(1,1);
101         scanf("%d",&m);
102         while (m--){
103             scanf("%d%d",&x,&d);
104             int ans=query(x,d);
105             printf("%d
",ans);
106         }
107     }
108     return 0;
109 }
原文地址:https://www.cnblogs.com/kkrisen/p/3688928.html