题目传送门:https://www.luogu.org/problemnew/show/P1600

感觉这两天在处理边界问题上有点神志不清......为了从80的暴力变成100,花了整整一个下午+一个晚上的时间(还好最后还是搞了出来)

题目大意:给你一棵树N个点的无根树,有M个人要从Si走到Ti,行走速度为每秒一条边。对于树上任意节点i,求出所有经过该点时行走时间恰好为Wi的路径数量。且这M个人到达终点后下一秒会立即消失。

先来说说暴力,写得妙的话,这题暴力可以拿80分(是不是很良心??)

这种题目考场上最好还是打暴力

25分(1到5号点):直接对于所有的任务,模拟从S跑到T,随后直接统计答案即可。

  1. bool dfs(int x,int fa,int t,int dep){
  2. if(x==t){
  3. if(dep==w[x]) ans[x]++;
  4. return ;
  5. }
  6. for(int i=head[x];i;i=e[i].next) if(e[i].u!=fa){
  7. if(dfs(e[i].u,x,t,dep+)){
  8. if(dep==w[x]) ans[x]++;
  9. return ;
  10. }
  11. }
  12. return ;
  13. }

25分暴力

另一个15分(6,7,8号点):我们可以将这M个人分为两类(Si>Ti和Si≤Ti),对于一个点i,我们通过二分统计Sj==i-W[i]和Sj==W[i]-i的数量,随后直接输出即可。

  1. if(op==){
  2. dep[]=-; dfs(,);
  3. for(int i=;i<=m;i++){
  4. if(!in[a[i].t])
  5. qx.push(a[i].t);
  6. have[a[i].t]++;
  7. in[a[i].t]=;
  8. }
  9. while(!qx.empty()){
  10. node u=qx.top(); qx.pop();
  11. int x=u.x;
  12. if(!in[f[x]]) qx.push(f[x]),in[f[x]]=;
  13. have[f[x]]+=have[x];
  14. }
  15. for(int i=;i<=n;i++)
  16. if(w[i]==dep[i])
  17. ans[i]=have[i];
  18. for(int i=;i<=n;i++) printf("%d ",ans[i]);
  19. return ;
  20. }

一条链的暴力

另一个20分(9,10,11,12号点):考虑Si=1,不妨设整棵树的根为1,随后求出所有点的深度dep[i]。不难发现观察员i能观察到的选手必从其父亲方向跑来。开一个优先队列,以dep[Ti]为关键字,存储所有的Ti,借此维护f数组,f[i]表示从根节点跑来的人中经过点i(包括以i为终点的人)的人的数量。若点i满足w[i]==dep[i],则ans[i]=f[i],否则ans[i]=0。

  1. if(op==){
  2. dep[]=-; dfs(,);
  3. for(int i=;i<=m;i++){
  4. if(!in[a[i].t])
  5. qx.push(a[i].t);
  6. have[a[i].t]++;
  7. in[a[i].t]=;
  8. }
  9. while(!qx.empty()){
  10. node u=qx.top(); qx.pop();
  11. int x=u.x;
  12. if(!in[f[x]]) qx.push(f[x]),in[f[x]]=;
  13. have[f[x]]+=have[x];
  14. }
  15. for(int i=;i<=n;i++)
  16. if(w[i]==dep[i])
  17. ans[i]=have[i];
  18. for(int i=;i<=n;i++) printf("%d ",ans[i]);
  19. return ;
  20. }

Si=1的20分暴力

另一个20分(13,14,15,16号点):由于Ti=1,假设整棵树根节点为1,不难发现所有观察员能观察到的选手必从其子节点跑来。先对整棵树进行广搜,处理dfs序和一个类似bfs序的东西(数组l和输入r),以及bfs序中选手出现次数的前缀和,其中l[x]表示深度为x的点在bfs序中最早出现的位置,r[x]表示深度为x的点在bfs序中最晚出现的位置。借助这些预处理出的数组,我们就可以通过二分在O(logn)的时间内求出以i为根的字树中深度为dep[i]+w[i]的节点数量,即答案。

  1. bool cmpd(int x,int y){
  2. return dfn[x]<=dfn[y];
  3. }
  4. bool cmpl(int x,int y){
  5. return low[x]<=low[y];
  6. }
  7. if(op==){
  8. dep[]=-; dfs(,); t=;
  9. for(int i=;i<=m;i++) have[a[i].s]++;
  10. int last=,lastdep=-; q.push();
  11. while(!q.empty()){
  12. int u=q.front(); q.pop();
  13. num[++t]=u;
  14. numsum[t]=numsum[t-]+have[u];
  15. if(dep[u]!=dep[last]){
  16. r[dep[last]]=t-; l[dep[u]]=t;
  17. lastdep=dep[last]; last=u;
  18. }
  19. for(int i=head[u];i;i=e[i].next) if(dep[e[i].u]!=lastdep){
  20. q.push(e[i].u);
  21. }
  22. }
  23. for(int i=;i<=n;i++){
  24. int ceng=dep[i]+w[i];
  25. if(!l[ceng]) continue;
  26. int ll=lower_bound(num+l[ceng],num+r[ceng]+,i,cmpd)-num;
  27. int rr=lower_bound(num+l[ceng],num+r[ceng]+,i,cmpl)-num;
  28. if(ll>=rr) continue;
  29. ans[i]=numsum[rr-]-numsum[ll-];
  30. }
  31. for(int i=;i<=n;i++) printf("%d ",ans[i]);
  32. return ;
  33. }

Ti=1的20分暴力

完整的80分组合暴力代码如下:

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. #include<algorithm>
  5. #include<queue>
  6. #define M 310000
  7. #define lowbit(x) (x&(-x))
  8. using namespace std;
  9. struct edge{int u,next;}e[M*]={}; int head[M]={},use=;
  10. void Add(int x,int y){use++;e[use].u=y;e[use].next=head[x]; head[x]=use;}
  11. struct st{
  12. int s,t; st(){s=t=;}
  13. st(int ss,int tt){s=ss; t=tt;}
  14. friend bool operator <(st a,st b){if(a.s==b.s) return a.t<b.t; return a.s<b.s;}
  15. }a[M],b[M];
  16. int n,m,w[M]={},ans[M]={},la[M]={},ra[M]={},lb[M]={},rb[M]={};
  17. bool dfs(int x,int fa,int t,int dep){
  18. if(x==t){
  19. if(dep==w[x]) ans[x]++;
  20. return ;
  21. }
  22. for(int i=head[x];i;i=e[i].next) if(e[i].u!=fa){
  23. if(dfs(e[i].u,x,t,dep+)){
  24. if(dep==w[x]) ans[x]++;
  25. return ;
  26. }
  27. }
  28. return ;
  29. }
  30. int dfn[M]={},low[M]={},t=,dep[M]={};
  31. int p[M]={},have[M]={},num[M]={},numsum[M]={},l[M]={},r[M]={};
  32. void add(int x,int k){
  33. for(int i=x;i<=n;i+=lowbit(i)) p[i]+=k;
  34. }
  35. int sum(int x){
  36. int k=; for(int i=x;i;i-=lowbit(i)) k+=p[i];
  37. return k;
  38. }
  39. int f[M]={};
  40. void dfs(int x,int fa){
  41. f[x]=fa;
  42. dep[x]=dep[fa]+; dfn[x]=++t;
  43. for(int i=head[x];i;i=e[i].next) if(e[i].u!=fa)
  44. dfs(e[i].u,x);
  45. low[x]=t;
  46. }
  47. queue<int> q;
  48.  
  49. bool cmpd(int x,int y){
  50. return dfn[x]<=dfn[y];
  51. }
  52. bool cmpl(int x,int y){
  53. return low[x]<=low[y];
  54. }
  55. struct node{
  56. int x; node(){x=;}
  57. node(int xx){x=xx;}
  58. friend bool operator <(node a,node b){return dep[a.x]<dep[b.x];}
  59. };
  60. priority_queue<node> qx;
  61. bool in[M]={};
  62. int main(){
  63. freopen("running.in","r",stdin);
  64. freopen("running.out","w",stdout);
  65. scanf("%d%d",&n,&m);
  66. for(int i=;i<n;i++){
  67. int x,y; scanf("%d%d",&x,&y);
  68. Add(x,y); Add(y,x);
  69. }
  70. for(int i=;i<=n;i++) scanf("%d",w+i);
  71. for(int i=;i<=m;i++) scanf("%d%d",&a[i].s,&a[i].t);
  72. int op=n%;
  73. if(op<=||op>=){
  74. for(int i=;i<=m;i++) dfs(a[i].s,,a[i].t,);
  75. for(int i=;i<=n;i++) printf("%d ",ans[i]);
  76. return ;
  77. }
  78. if(op==){
  79. sort(a+,a+m+);
  80. int na=,nb=;
  81. for(int i=;i<=n;i++){
  82. if(a[i].s<=a[i].t) a[++na]=a[i];
  83. else b[++nb]=a[i];
  84. }
  85. a[na+]=st(,);
  86. for(int i=;i<=na+;i++){
  87. if(a[i].s==a[i-].s) continue;
  88. ra[a[i-].s]=i-; la[a[i].s]=i;
  89. }
  90. for(int i=;i<=nb+;i++){
  91. if(b[i].s==b[i-].s) continue;
  92. rb[b[i-].s]=i-; lb[b[i].s]=i;
  93. }
  94. for(int i=;i<=n;i++){
  95. int lid=i-w[i],rid=i+w[i];
  96. if(lid>&&la[lid]){
  97. int id=lower_bound(a+la[lid],a+ra[lid]+,st(lid,i))-a;
  98. if(id==ra[lid]+) goto loop;
  99. ans[i]+=ra[lid]-id+;
  100. }
  101. loop:;
  102. if(rid<=n&&lb[rid]){
  103. int id=upper_bound(b+lb[rid],b+rb[rid]+,st(rid,i))-b;
  104. ans[i]+=id-lb[rid];
  105. }
  106. }
  107. for(int i=;i<=n;i++) printf("%d ",ans[i]);
  108. return ;
  109. }
  110. if(op==){
  111. dep[]=-; dfs(,);
  112. for(int i=;i<=m;i++){
  113. if(!in[a[i].t])
  114. qx.push(a[i].t);
  115. have[a[i].t]++;
  116. in[a[i].t]=;
  117. }
  118. while(!qx.empty()){
  119. node u=qx.top(); qx.pop();
  120. int x=u.x;
  121. if(!in[f[x]]) qx.push(f[x]),in[f[x]]=;
  122. have[f[x]]+=have[x];
  123. }
  124. for(int i=;i<=n;i++)
  125. if(w[i]==dep[i])
  126. ans[i]=have[i];
  127. for(int i=;i<=n;i++) printf("%d ",ans[i]);
  128. return ;
  129. }
  130. if(op==){
  131. dep[]=-; dfs(,); t=;
  132. for(int i=;i<=m;i++) have[a[i].s]++;
  133. int last=,lastdep=-; q.push();
  134. while(!q.empty()){
  135. int u=q.front(); q.pop();
  136. num[++t]=u;
  137. numsum[t]=numsum[t-]+have[u];
  138. if(dep[u]!=dep[last]){
  139. r[dep[last]]=t-; l[dep[u]]=t;
  140. lastdep=dep[last]; last=u;
  141. }
  142. for(int i=head[u];i;i=e[i].next) if(dep[e[i].u]!=lastdep){
  143. q.push(e[i].u);
  144. }
  145. }
  146. for(int i=;i<=n;i++){
  147. int ceng=dep[i]+w[i];
  148. if(!l[ceng]) continue;
  149. int ll=lower_bound(num+l[ceng],num+r[ceng]+,i,cmpd)-num;
  150. int rr=lower_bound(num+l[ceng],num+r[ceng]+,i,cmpl)-num;
  151. if(ll>=rr) continue;
  152. ans[i]=numsum[rr-]-numsum[ll-];
  153. }
  154. for(int i=;i<=n;i++) printf("%d ",ans[i]);
  155. return ;
  156. }
  157. }

其实光Ti=1的情况,就已经称得上NOIP day1 T2了.....

下面说下正解:

题目的数据范围(特别是Ti和Si等于1的情况)是特别有启发作用的。对于一组路径(Si,Ti),我们可以考虑将它划分为两个询问(Si,lca)和(lca,Ti),分别进行求和。

考虑从Si到lca(即从下往上的走)的情况,若一组路径能对点x产生贡献,则必然满足dep[x]+w[x]==dep[Si]且dep[x]≤dep[lca]。我们可以考虑维护一个数组cnt,cnt[i]表示dep[v]==i的节点v的数量。对于每个节点x,维护一个数组cnt,且节点v的范围仅限于以x为根的子树。则该部分答案为cnt[dep[x]+w[x]]。cnt向其父节点fa[x]回溯时,减去所有以x为lca的路径对cnt产生的贡献。该过程仅需一遍dfs

考虑lca到Ti(从上往下走)的情况,若能对点x产生贡献,则必然满足dep[x]-w[x]==dep[lca]-dis(Si,lca)。同理开一个数组cnt,cnt[i]表示dep[v]-dis(Si,lca)==i的节点v的数量。其余处理部分与Si到lca相同。

上述两种方法,分别需要n个cnt数组,且单次更新其父节点cnt值所需时间为O(n),空间复杂度和事件复杂度均为O(n^2),直接写显然是不行的。我们考虑使用权值线段树去维护cnt数组,同时处理出整棵树后序遍历的dfs序。假设当前需求Si到lca的路径对x的贡献,则贡献为cnt[low[x]][dep[x]+w[x]]-cnt[dfn[x]-1][dep[x]+w[x]]。同理可得lca到Ti对x的贡献。

至此,时间复杂度和空间复杂度均降低至O(n log n)。

PS:该方法需处理很多边界问题,请特别注意!(比如说统计lca处的答案),同时在处理lca到Ti的情况时,cnt[i]的下标可能为负数,需要做一些特殊处理。

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. #include<vector>
  5. #define M 310000
  6. using namespace std;
  7. struct edge{int u,next;}e[M*]={}; int head[M]={},use=;
  8. void adde(int x,int y){use++;e[use].u=y;e[use].next=head[x]; head[x]=use;}
  9. int f[M][]={},dep[M]={};
  10. int w[M]={},s[M]={},t[M]={},lca[M]={},n,m;
  11. int Cnt[M*]={},*cnt=&Cnt[M*],ans[M]={},Add[M]={};
  12. vector<int> add[M],del[M],Del[M];
  13. void dfs(int x,int fa){
  14. dep[x]=dep[fa]+; f[x][]=fa;
  15. for(int i=;i<;i++) f[x][i]=f[f[x][i-]][i-];
  16. for(int i=head[x];i;i=e[i].next) if(e[i].u!=fa) dfs(e[i].u,x);
  17. }
  18. int getlca(int x,int y){
  19. if(dep[x]<dep[y]) swap(x,y); int cha=dep[x]-dep[y];
  20. for(int i=;i>=;i--) if((<<i)&cha) x=f[x][i];
  21. for(int i=;i>=;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
  22. if(x!=y) return f[x][]; return x;
  23. }
  24.  
  25. struct sgt{int lx,rx,num;}a[M*];
  26. int root[M]={},dfn[M]={},low[M]={},T=,nuse=;
  27. int query(int x,int k,int l,int r){
  28. if(!x||k>r) return ;//注意在向下找深度更深的点时k可能会>r
  29. if(l==r) return a[x].num;
  30. int mid=(l+r)>>;
  31. if(k<=mid) return query(a[x].lx,k,l,mid);
  32. else return query(a[x].rx,k,mid+,r);
  33. }
  34. int updata(int x,int k,int l,int r,int zhi){
  35. int nowid=++nuse;
  36. a[nowid].lx=a[x].lx; a[nowid].rx=a[x].rx; a[nowid].num=a[x].num;
  37. if(l==r) a[nowid].num+=zhi;
  38. else{
  39. int mid=(l+r)>>;
  40. if(k<=mid) a[nowid].lx=updata(a[nowid].lx,k,l,mid,zhi);
  41. else a[nowid].rx=updata(a[nowid].rx,k,mid+,r,zhi);
  42. }
  43. return nowid;
  44. }
  45.  
  46. void dfs2(int x,int fa){//由lca到t
  47. dfn[x]=T;
  48. for(int i=head[x];i;i=e[i].next) if(e[i].u!=fa){
  49. dfs2(e[i].u,x);
  50. }
  51. low[x]=++T; root[T]=root[T-];
  52. int siz=add[x].size();
  53. for(int i=;i<siz;i++)
  54. root[T]=updata(root[T],dep[x]-add[x][i],-n,n,);
  55. siz=del[x].size();
  56. ans[x]+=query(root[T],dep[x]-w[x],-n,n)-query(root[dfn[x]],dep[x]-w[x],-n,n);
  57. for(int i=;i<siz;i++)
  58. root[T]=updata(root[T],dep[x]-del[x][i],-n,n,-);
  59. }
  60.  
  61. void dfs3(int x,int fa){//由s到lca
  62. dfn[x]=T;
  63. for(int i=head[x];i;i=e[i].next) if(e[i].u!=fa){
  64. dfs3(e[i].u,x);
  65. }
  66. low[x]=++T;
  67. root[T]=updata(root[T-],dep[x],,n,Add[x]);
  68. int siz=Del[x].size();
  69. for(int i=;i<siz;i++)
  70. root[T]=updata(root[T],dep[x]+Del[x][i],,n,-);
  71. ans[x]+=query(root[T],dep[x]+w[x],,n)-query(root[dfn[x]],dep[x]+w[x],,n);
  72. }
  73.  
  74. int main(){
  75. freopen("running.in","r",stdin);
  76. freopen("running.out","w",stdout);
  77. scanf("%d%d",&n,&m);
  78. for(int i=;i<n;i++){
  79. int x,y; scanf("%d%d",&x,&y);
  80. adde(x,y); adde(y,x);
  81. }
  82. dfs(,);
  83. for(int i=;i<=n;i++) scanf("%d",w+i);
  84. for(int i=;i<=m;i++){
  85. int s,t,lca; scanf("%d%d",&s,&t);
  86. lca=getlca(s,t);
  87. add[t].push_back(dep[s]+dep[t]-*dep[lca]);
  88. del[lca].push_back(dep[s]-dep[lca]);
  89. Add[s]++;
  90. Del[lca].push_back(dep[s]-dep[lca]);
  91. //以上这几句话真的累死我了.....
  92. }
  93. dfs2(,);
  94. memset(a,,sizeof(a)); memset(root,,sizeof(root)); T=;
  95. memset(dfn,,sizeof(dfn)); memset(low,,sizeof(low)); nuse=;
  96. dfs3(,);
  97. for(int i=;i<=n;i++) printf("%d ",ans[i]);
  98. }

【NOIP2016 Day1 T2】天天爱跑步的更多相关文章

  1. [NOIP2016 DAY1 T2]天天爱跑步-[差分+线段树合并][解题报告]

    [NOIP2016 DAY1 T2]天天爱跑步 题面: B[NOIP2016 DAY1]天天爱跑步 时间限制 : - MS 空间限制 : 565536 KB 评测说明 : 2s Description ...

  2. Luogu P1600[NOIP2016]day1 T2天天爱跑步

    号称是noip2016最恶心的题 基本上用了一天来搞明白+给sy讲明白(可能还没讲明白 具体思路是真的不想写了(快吐了 如果要看,参见洛谷P1600 天天爱跑步--题解 虽然这样不好但我真的不想写了 ...

  3. NOIP2016 DAY1 T2天天爱跑步

    传送门 题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.«天天爱跑步»是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏的地图可以看作一一棵包含 ...

  4. NOIP2016 Day1 T2 天天爱跑步(树上差分,LCA)

    原文链接 原题链接 题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏 ...

  5. 【NOIP2016】DAY1 T2 天天爱跑步

    [NOIP2016]DAY1 T2 天天爱跑步 Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.?天天爱跑步?是一个养成类游戏,需要玩家每天按时 ...

  6. 【NOIP 2016】Day1 T2 天天爱跑步

    Problem Description 小 C 同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任 ...

  7. [NOIp2016提高组]天天爱跑步

    题目大意: 有一棵n个点的树,每个点上有一个摄像头会在第w[i]秒拍照. 有m个人再树上跑,第i个人沿着s[i]到t[i]的路径跑,每秒钟跑一条边. 跑到t[i]的下一秒,人就会消失. 问每个摄像头会 ...

  8. P1600 [NOIP2016 提高组] 天天爱跑步 (树上差分)

    对于一条路径,s-t,位于该路径上的观察员能观察到运动员当且仅当以下两种情况成立:(d[ ]表示节点深度) 1.观察员x在s-lca(s,t)上时,满足d[s]=d[x]+w[x]就能观察到,所以我们 ...

  9. [luogu]P1600 天天爱跑步[LCA]

    [luogu]P1600 [NOIP 2016]天天爱跑步 题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.«天天爱跑步»是一个养成类游戏,需要玩家每天按时上 ...

随机推荐

  1. 《JAVA程序设计》第11周学习总结

    1. 本章学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. synchronized方法/代码块 wait().notify()用法,生产者消费者例子 lock.condit ...

  2. Spring Boot Maven Plugin(一):repackage目标

    简介 Spring Boot Maven Plugin插件提供spring boot在maven中的支持.允许你打包可运行的jar包或war包. 插件提供了几个maven目标和Spring Boot ...

  3. Qt Creator编译运行成功,但是显示系统找不到指定的文件(比如urlmon.dll动态链接库)

    问题: 以前自己写的一个QT界面程序,在win 7 的32位系统上运行没有出现任何问题,但是重装系统之后,同样的程序放到win10 的64位系统下运行会出现警告:onecoreuap\inetcore ...

  4. vim下处理文档中的\r\n\t字符

    问题复现 拿到的文档中包含了大量的\r.\n.\t等字符,形如: \r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\ ...

  5. JVM 菜鸟进阶高手之路九(解惑)

    转载请注明原创出处,谢谢! 在第八系列最后有些疑惑的地方,后来还是在我坚持不懈不断打扰笨神,阿飞,ak大神等,终于解决了该问题.第八系列地址:http://www.cnblogs.com/lirenz ...

  6. ArcGIS连带文字注记导出为CAD格式

    可以使用ArcGIS的"Export To CAD"工具将点.线.面等要素直接导出为CAD格式.如果要连带将ArcGIS中的文字标注导出为CAD格式要稍麻烦一点,下面是一个例子. ...

  7. 三、js的函数

    三.函数 函数是定义一次但却可以调用或执行任意多次的一段JS代码.函数有时会有参数,即函数被调用时指定了值的局部变量.函数常常使用这些参数来计算一个返回值,这个值也成为函数调用表达式的值. 1.函数声 ...

  8. 关于高德地图Android开发时地图只显示一次、第二次打开不定位的解决办法

    我按照高德官方Demo改的 第一次是可以定位的,如左图 第二次就不能定位了,如右图 在onDestory中把aMap置为空即可 aMap = null; 修改完如下图: 原理是第二次打开时aMap不为 ...

  9. 认识 Java Message Service

    1. Java Message Service : 是一个消息服务的标准或者说是规范,允许应用程序组件基于JavaEE平台创建.发送.接收和读取消息. 实现Java 程序与MQ Server 之间互相 ...

  10. leetCode没那么难啦 in Java (二)

    介绍    本篇介绍的是标记元素的使用,很多需要找到正确元素都可以将正确元素应该插入的位置单独放置一个标记来记录,这样可以达到原地排序的效果. Start 27.RemoveElement 删除指定元 ...