Description

小c同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。这个游戏的地图可以看作一一棵包含 N个结点和N-1 条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从1到N的连续正整数。现在有个玩家,第个玩家的起点为Si ,终点为Ti  。每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发, 以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去, 跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树, 所以每个人的路径是唯一的)小C想知道游戏的活跃度, 所以在每个结点上都放置了一个观察员。 在结点的观察员会选择在第Wj秒观察玩家, 一个玩家能被这个观察员观察到当且仅当该玩家在第Wj秒也理到达了结点J  。 小C想知道每个观察员会观察到多少人?注意: 我们认为一个玩家到达自己的终点后该玩家就会结束游戏, 他不能等待一 段时间后再被观察员观察到。 即对于把结点J作为终点的玩家: 若他在第Wj秒重到达终点,则在结点J的观察员不能观察到该玩家;若他正好在第Wj秒到达终点,则在结点的观察员可以观察到这个玩家。

Input

第一行有两个整数n和m。其中n代表树的结点数量,同时也是观察员的数量,m代表玩家的数量。
接下来n-1行每行两个整数u和v,表示结点u到结点v有一条边。
接下来一行n个整数,其中第j个整数为Wj,表示结点j出现观察员的时间。
接下来m行,每行两个整数Si和Ti,表示一个玩家的起点和终点。
对于所有的数据,保证1≤Si,Ti≤n,0≤ Wj ≤n。

Output

输出1行n个整数,第j个整数表示结点j的观察员可以观察到多少人。

Sample Input

6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6

Sample Output

2 0 0 1 1 1

Hint

提示:
对于1号点,W1=0,故只有起点为1号点的玩家才会被观察到,所以玩家1和玩家2被观察到,共2人被观察到。
对于2号点,没有玩家在第2秒时在此结点,共0人被观察到。
对于3号点,没有玩家在第5秒时在此结点,共0人被观察到。
对于4号点,玩家1被观察到,共1人被观察到。
对于5号点,玩家2被观察到,共1人被观察到。
对于6号点,玩家3被观察到,共1人被观察到。

如果你的程序需要用到较大的栈空间(这通常意味着需要较深层数的递归),请务必仔细阅读选手目录下的文档running/stackpdf,以了解在最终评测时栈空间的限制与在当前工作环境下调整栈空间限制的方法。

Solution

以下部分分算法引自 传送门

测试点1——5:

数组a[i][j]表示点i在j时刻经过的人数,然后一个人一个人的dfs,记录时刻time

如果到了终点,递归回退时a[now][time]++

令watch[i]=j表示i号节点观察员在j时刻出现

输出a[i][watch[i]]

  1. void run(int now,int end,int time,int fa)
  2. {
  3. if(now==end)
  4. {
  5. ok=true;
  6. a[now][time]++;
  7. return;
  8. }
  9. if(ok) return;
  10. for(int i=front[now];i;i=nextt[i])
  11. {
  12. if(to[i]==fa) continue;
  13. run(to[i],end,time+,now);
  14. }
  15. if(ok) a[now][time]++;
  16. }

25分

测试点6——8:

树退化为一条链,而且点的编号就是深度

所以对于链上一个观察员,他只能观察到从两个位置出发的人i+watch[i],i-watch[i]

如果i能观察到起点深度比他小的人,那么这个人的终点要>=i

如果i能观察到起点深度比他大的人,那么这个人的终点要<=i

所以,

用链表存储从每个节点起跑的人,

枚举每个观察员,然后判断两个位置的人是否满足要求

  1. void solve2()
  2. {
  3. for(int i=;i<=n;i++)
  4. {
  5. if(i-watch[i]>=)
  6. {
  7. for(int j=front[i-watch[i]];j;j=nextt[j])
  8. {
  9. if(i<=e[to[j]].t) ans[i]++;
  10. }
  11. }
  12. if(i+watch[i]<=n)
  13. {
  14. for(int j=front[i+watch[i]];j;j=nextt[j])
  15. {
  16. if(i>=e[to[j]].t) ans[i]++;
  17. }
  18. }
  19. }
  20. }

40分

测试点9——12:

所有人的起点都是1,假设1号的深度为0,

那么只有观察员节点的深度等于出现时间,他才能观察到

所以先判断深度是否等于出现时间,不等,直接输出0,下一个

若相等,他一定能观察到经过他的所有人

也就是说,以观察员i为根的子树中有跑步者的终点,观察员i就能观察多少人

所以,终点+1,记录每个节点的深度,我用的bfs,单后dfs统计子树1的个数,

kids[]表示答案

  1. void bfs1()
  2. {
  3. queue<node2>q;
  4. cur.d= ;cur.who=; fa[]=;
  5. q.push(cur);
  6. while(!q.empty())
  7. {
  8. cur=q.front(); q.pop();
  9. for(int i=front[cur.who];i;i=nextt[i])
  10. {
  11. if(to[i]==fa[cur.who]) continue;
  12. fa[to[i]]=cur.who;
  13. nxt.d=cur.d+; nxt.who=to[i]; deep[nxt.who]=deep[cur.who]+;
  14. q.push(nxt);
  15. }
  16. }
  17. }
  18. void dfs1(int now)
  19. {
  20. kids[now]=sum[now];
  21. for(int i=front[now];i;i=nextt[i])
  22. {
  23. if(to[i]==fa[now]) continue;
  24. dfs1(to[i]);
  25. kids[now]+=kids[to[i]];
  26. }
  27. }

60分

测试点13——16:

所有人的终点都是1

也就是说,第i个观察员只能观察到第deep[i]+watch[i]层的跑步者

所以,若跑步者能被观察员i观察到,要满足以下两个条件:

1、在起点观察员i的子树内

2、起点的层数=deep[i]+watch[i]

先对原树dfs一遍,记录观察员i的子树dfs序范围in[i]——out[i]

对每一层维护一颗线段树,动态开节点

查询时,找到deep[i]+watch[i]这一层的线段树,查in[i]——out[i]有多少个起点

  1. void add(int &now,int pos,int l,int r,int cnt)
  2. {
  3. if(!now) now=++tot2;
  4. sum[now]+=cnt;
  5. if(l==r) return;
  6. int mid=l+r>>;
  7. if(pos<=mid) add(lc[now],pos,l,mid,cnt);
  8. else add(rc[now],pos,mid+,r,cnt);
  9. }
  10. void dfs(int now,int pre)
  11. {
  12. in[now]=++tot;
  13. add(root[deep[now]],tot,,n,siz[now]);
  14. for(int i=front[now];i;i=nxt[i])
  15. {
  16. if(to[i]==pre) continue;
  17. deep[to[i]]=deep[now]+;
  18. maxn=max(maxn,deep[to[i]]);
  19. dfs(to[i],now);
  20. }
  21. out[now]=tot;
  22. }
  23. void query(int now,int opl,int opr,int l,int r)
  24. {
  25. if(!now) return;
  26. if(l>=opl&&r<=opr) { ans+=sum[now]; return; }
  27. int mid=l+r>>;
  28. if(opl<=mid) query(lc[now],opl,opr,l,mid);
  29. if(opr>mid) query(rc[now],opl,opr,mid+,r);
  30. }

-----------------------------------------------引用内容结束-----------------------------------------------------

正解

对于一条路径u->v,我们可以剖成往上和往下的两部分

对于向上的u->lca,如果中间有点i满足dep[i]+w[i]==dep[u],则这条路径会对i的答案产生贡献

对于向下的lca->v,如果中间有点i满足dep[i]-w[i]==dep[v]-dis(u,v),则这条路径会对i的答案产生贡献

那么我们考虑在dfs时维护每一层的起点数,对于向上的时候,对答案产生贡献开始于u点,终止于lca点,对于向下的时候,对答案产生贡献开始于v点,终止于lca点。

那么我们在一开始用三个数组v1(i)记录下路径lca为i的起点深度,v2(i)记录下路径终点为i的终点深度-dis(路径起点,路径终点),v3(i)记录下路径lca为i的终点深度-dis(路径起点,路径终点)

我们就可以计算向上跑时,在dfs每一个点的时候 向该层加入该点的路径数,统计答案,删除以该点为lca的路径数

同理向下跑 向该层加入终点为该点的路径数,统计答案,删除以该点为lca的路径数

Code

  1. #include<bits/stdc++.h>
  2. #define RG register int
  3. #define rep(i,a,b) for(RG i=a;i<=b;++i)
  4. #define add(u,v) e[++cnt].v=v,e[cnt].next=head[u],head[u]=cnt,swap(u,v),e[++cnt].v=v,e[cnt].next=head[u],head[u]=cnt
  5. #define maxn 300100
  6. #define WY 300000
  7. using namespace std;
  8. int n,m,cnt;
  9. int fa[maxn],son[maxn],sz[maxn],top[maxn],dep[maxn],head[maxn],num[maxn],tot[maxn<<],ans[maxn],w[maxn];
  10. vector<int> v1[maxn],v2[maxn],v3[maxn];
  11. struct E{
  12. int v,next;
  13. }e[maxn<<];
  14. inline int read()
  15. {
  16. int x=,f=;char c=getchar();
  17. while(c<''||c>''){if(c=='-')f=-;c=getchar();}
  18. while(c>=''&&c<=''){x=x*+c-'';c=getchar();}
  19. return x*f;
  20. }
  21. void dfs1(int u,int pa)
  22. {
  23. sz[u]=,fa[u]=pa,dep[u]=dep[pa]+;
  24. for(int i=head[u];i;i=e[i].next)
  25. {
  26. int v=e[i].v; if(v==pa)continue;
  27. dfs1(v,u); sz[u]+=sz[v];
  28. if(sz[son[u]]<sz[v]) son[u]=v;
  29. }
  30. }
  31. void dfs2(int u,int tp)
  32. {
  33. top[u]=tp;
  34. if(!son[u]) return;dfs2(son[u],tp);
  35. for(int i=head[u];i;i=e[i].next)
  36. if(e[i].v!=fa[u]&&e[i].v!=son[u]) dfs2(e[i].v,e[i].v);
  37. }
  38. int find(int x,int y)
  39. {
  40. while(top[x]!=top[y]){
  41. if(dep[top[x]]<dep[top[y]]) swap(x,y); x=fa[top[x]];
  42. }
  43. return dep[x]<dep[y]?x:y;
  44. }
  45. void go_up(int u)
  46. {
  47. int len=dep[u]+w[u]; //计算 离 对目前位置可以产生贡献的起点 的距离
  48. ans[u]-=tot[len]; //减去其他子树中的数量
  49. for(int i=head[u];i;i=e[i].next)
  50. if(e[i].v!=fa[u]) go_up(e[i].v);
  51. tot[dep[u]]+=num[u]; //加入目前点的起点数
  52. ans[u]+=tot[len]; //计算贡献
  53. for(int i=,lim=v1[u].size();i<lim;i++) --tot[v1[u][i]]; //减去桶中lca为该点的统计
  54. }
  55. void go_down(int u)
  56. {
  57. int len=dep[u]-w[u]+WY; //加上定值防止len<0
  58. ans[u]-=tot[len]; //减去其他子树中的数量
  59. for(int i=head[u];i;i=e[i].next)
  60. if(e[i].v!=fa[u]) go_down(e[i].v);
  61. for(int i=,lim=v2[u].size();i<lim;i++) ++tot[v2[u][i]]; //加入范围内(v==now)的点
  62. ans[u]+=tot[len];
  63. for(int i=,lim=v3[u].size();i<lim;i++) --tot[v3[u][i]]; //减去范围外(lca==now)的点
  64. }
  65. int main()
  66. {
  67. n=read(),m=read();
  68. for(RG i=,u,v;i<n;i++) u=read(),v=read(),add(u,v);
  69. dfs1(,);dfs2(,);
  70. rep(i,,n) w[i]=read();
  71. rep(i,,m)
  72. {
  73. int u=read(),v=read(),lca=find(u,v);
  74. ++num[u];int len=dep[u]+dep[v]-(dep[lca]<<);
  75. v1[lca].push_back(dep[u]),v2[v].push_back(dep[v]-len+WY),v3[lca].push_back(dep[v]-len+WY);
  76. if(w[lca]+dep[lca]==dep[u]) --ans[lca];
  77. }
  78. go_up();memset(tot,,sizeof(tot));
  79. go_down();
  80. rep(i,,n) printf("%d ",ans[i]);
  81. return ;
  82. }

天天爱跑步 [NOIP2016]的更多相关文章

  1. luoguP1600 天天爱跑步(NOIP2016)(主席树+树链剖分)

    阅读体验: https://zybuluo.com/Junlier/note/1303550 为什么这一篇的Markdown炸了? # 天天爱跑步题解(Noip2016)(桶+树上差分 ^ 树剖+主席 ...

  2. LOJ #2359. 「NOIP2016」天天爱跑步(倍增+线段树合并)

    题意 LOJ #2359. 「NOIP2016」天天爱跑步 题解 考虑把一个玩家的路径 \((x, y)\) 拆成两条,一条是 \(x\) 到 \(lca\) ( \(x, y\) 最近公共祖先) 的 ...

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

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

  4. [NOIp2016]天天爱跑步 线段树合并

    [NOIp2016]天天爱跑步 LG传送门 作为一道被毒瘤出题人们玩坏了的NOIp经典题,我们先不看毒瘤的"动态爱跑步"和"天天爱仙人掌",回归一下本来的味道. ...

  5. NOIP2016天天爱跑步 题解报告【lca+树上统计(桶)】

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

  6. [Noip2016]天天爱跑步 LCA+DFS

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

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

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

  8. 洛谷 题解 P1600 【天天爱跑步】 (NOIP2016)

    必须得说,这是一道难题(尤其对于我这样普及组205分的蒟蒻) 提交结果(NOIP2016 天天爱跑步): OJ名 编号 题目 状态 分数 总时间 内存 代码 / 答案文件 提交者 提交时间 Libre ...

  9. 【LG1600】[NOIP2016]天天爱跑步

    [LG1600][NOIP2016]天天爱跑步 题面 洛谷 题解 考虑一条路径\(S\rightarrow T\)是如何给一个观测点\(x\)造成贡献的, 一种是从\(x\)的子树内出来,另外一种是从 ...

随机推荐

  1. 使用python调用shell判断当前进程是否存在

    使用subprocess模块判断当前进程是否存在 #! /usr/bin/env python import subprocess res = subprocess.Popen(r'ps -ef |g ...

  2. base | AtomicIntegerT类

    1. 原子自增操作 type __sync_fetch_and_add (type *ptr, type value) 2. 原子比较和交换(设置)操作 type __sync_val_compare ...

  3. KnockoutJs学习笔记(五)

    作为一名初学者来说,一篇篇的按顺序看官网上的文档的确是一件很痛苦的事情,毕竟它的排列也并非是由浅及深的排列,其中的顺序也颇耐人寻味,于是这篇文章我又跳过了Reference部分,进而进入到具体的bin ...

  4. 【Android】Android 监听apk安装替换卸载广播

    [Android]Android 监听apk安装替换卸载广播 首先是要获取应用的安装状态,通过广播的形式 以下是和应用程序相关的Broadcast Action ACTION_PACKAGE_ADDE ...

  5. centos环境gcc版本升级

    今天项目需要做node.js项目的性能测试,通过在centos上搭建nodejs环境 安装过程中提示:

  6. 将input或textarea设置为disabled的样式问题

    input:disabled{ -webkit-text-fill-color: #333;//是用来做填充色使用的 -webkit-opacity: 1; color: #333; } textar ...

  7. jenkins(1): jenkins安装以及从gitlab拉取代码

    1. gitlab前面已经写过了,自己去参考 https://www.cnblogs.com/yitianyouyitian/p/9214940.html 2. jenkins安装 2.1 jdk 安 ...

  8. Dubbo入门---搭建一个最简单的Demo框架(转)

    Dubbo背景和简介 Dubbo开始于电商系统,因此在这里先从电商系统的演变讲起. 单一应用框架(ORM) 当网站流量很小时,只需一个应用,将所有功能如下单支付等都部署在一起,以减少部署节点和成本.  ...

  9. ERROR 1215 (HY000): Cannot add foreign key constraint

    MySQL中在为一个varchar类型数据列添加外键时,会发生上面所示的错误,这里我google了一下,感觉它们碰到的问题跟我这个说的有点不相干,尝试了多种方式后来才发现是:主表(table1)所对应 ...

  10. JavaScript深拷贝实现原理简析

    原文:http://www.cnblogs.com/xie-zhan/p/6020954.html JavaScript实现继承的时候,需要进行对象的拷贝:而为了不影响拷贝后的数据对原数据造成影响,也 ...