NOIP2016天天爱跑步

          2557. [NOIP2016]天天爱跑步

时间限制:2 s   内存限制:512 MB

【题目描述】

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

这个游戏的地图可以看作一棵包含n个结点和n-1条边的树,每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从1到n的连续正整数。

现在有m个玩家,第i个玩家的起点为Si,终点为Ti。每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发,以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去,跑到终点后该玩家就算完成了打卡任务。(由于地图是一棵树,所以每个人的路径是唯一的)

小C想知道游戏的活跃度, 所以在每个结点上都放置了一个观察员。 在结点的观察员会选择在第Wj秒观察玩家, 一个玩家能被这个观察员观察到当且仅当该玩家在第Wj秒也理到达了结点J  。 小C想知道每个观察员会观察到多少人?

注意: 我们认为一个玩家到达自己的终点后该玩家就会结束游戏, 他不能等待一 段时间后再被观察员观察到。 即对于把结点J作为终点的玩家: 若他在第Wj秒重到达终点,则在结点J的观察员不能观察到该玩家;若他正好在第Wj秒到达终点,则在结点的观察员可以观察到这个玩家。

【输入格式】

第一行有两个整数n和m。其中n代表树的结点数量,同时也是观察员的数量,m代表玩家的数量。

接下来n-1行每行两个整数u和v,表示结点u到结点v有一条边。

接下来一行n个整数,其中第j个整数为Wj,表示结点j出现观察员的时间。

接下来m行,每行两个整数Si和Ti,表示一个玩家的起点和终点。

对于所有的数据,保证1≤Si,Ti≤n,0≤ Wj ≤n。

【输出格式】

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

【样例1输入】

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

【样例1输出】

2 0 0 1 1 1

【样例2输入】

5 3

1 2

2 3

2 4

1 5

0 1 0 3 0

3 1

1 4

5 5

【样例2输出】

1 2 1 0 1

【提示】

对于1号点,W1=0,故只有起点为1号点的玩家才会被观察到,所以玩家1和玩家2被观察到,共2人被观察到。

对于2号点,没有玩家在第2秒时在此结点,共0人被观察到。

对于3号点,没有玩家在第5秒时在此结点,共0人被观察到。

对于4号点,玩家1被观察到,共1人被观察到。

对于5号点,玩家2被观察到,共1人被观察到。

对于6号点,玩家3被观察到,共1人被观察到。

  感觉这是一道好难的题,,别人花10分钟打dfs拿了暴力25分,我花了快一个小时打树链刨分维护线段树也才拿了25分呜呜呜~~~~(>_<)~~~~

上网搜别人的题解发现都讲得懵懵懂懂的,反正自己是理解了好半天的呢!

  我答的应该还是一些比较容易懂的方法吧..

解题报告::

    发现数据n,m都是30W以上的,果断确定算法O(nlogn) ,而且只能选择一个,要么处理人,要么处理观察员,我们在这里用一下经典思路:两个节点S,T之间的距离,可以把它看成从S到lca(最近公共祖先),从lca再到T的过程,对这两部分过程进行分段处理就好了。

    ①:从S到lca的过程中,一个观察员i如果能够观察到跑步人x的话,那一定满足式子dp[i]-dp[x]==w[i](dp是深度,w是时间,因为根节点的深度为0,并且每一条边的长度都为一,所以dp数组也可以看成是到根节点的距离),我们对他进行一下移项处理:dp[x]=dp[i]-w[i],可以发现等式的右边是一个定值,所以这时候就比较容易想到了,我们对跑步的人进行处理,询问时查询每一个节点的dp[i]-w[i]即可。首先用树链剖分预处理出dp数组,求出每个人起点和终点的lca;然后进行动态插点,因为询问时,如果观察员能观察到这个人,那么dp[i]-w[i]一定等于dp[x],并且观察到同一个人的所有观察员肯定满足dp[i]-w[i]==dp[j]-w[j];所以我们对每一个深度dp[S]进行插入,如何解决内存的问题呢??用线段树的动态开点就好了,

    ②:返回来同理:dp[i]+dp[S]-2*dp[lca]=w[i],进行移项处理:dp[S]-2*dp[lca]==w[i]-dp[i];原理同上,但因为是减法,会出负数,所以整体右移,这就会使内存很大很大,开数组的时候一定要注意,我在这里跪了几次(⊙o⊙)…,在处理完第一个时候,用一个数组存储答案,然后先前的那些就没有用了,反而会对后面的产生干扰,所以在求第二次的时候,不要忘记清空线段树,还有个细节要注意,因为lca是只经过一次的,但是你两个路径中却都有,所以你要舍一个,用fa[lca]来当上界了。

  补充:因为一个观察员能观察到人当且仅当这个人的路线是经过观察员的,所以插入的时候要用到差分和dfs序的思想,一个点在进入时有一个时间戳,出来是有一个时间戳,那么在插入操作时,以第一段为例,只要在起点的时间戳出+1,再其fa[lca]处-1(包含lca),这样如果这个人是经过观察员的,那么观察员进出的时间戳中就构成了一个连续的区间,这个区间中一定会有起点的那个+1,没有lca的-1,如果没有经过,要么是区间中不含+1,或者是+1与-1抵消了,这样都不会产生任何影响的;、

    所以最后对每个节点的观察员来说,答案即为在dp[i]-w[i]询问其子树中的数量+第二次w[i]-dp[i]+2*n(为了防止出负数,整体右移了2*n,数组也要相应的扩大更多)询问其子树中的数量之和

输出答案即可

  总结:这道题真的好难想啊,树链剖分求lca+树上差分+动态开点线段树+最最难想到的那个式子

╮(╯▽╰)╭貌似这道题可用DP和DFS两种方法水过??(这个方法才叫水吧)。

  附码:

    

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstring>
  4 #include<algorithm>
  5 using namespace std;
  6 int n,m,num,x,y,dex;
  7 int w[300000],adj[300000];
  8 int rt[7000000],lc[7000000],rc[7000000];
  9 struct edge{
 10     int s,t,next;
 11 }k[600000];
 12 struct sta{
 13     int s,t,lca;
 14 }l[300000];
 15 int read(){
 16     int sum=0;char ch=getchar();
 17     while(ch<‘0‘||ch>‘9‘) ch=getchar();
 18     while(ch>=‘0‘&&ch<=‘9‘){sum=sum*10+ch-‘0‘;ch=getchar();}
 19     return sum;
 20 }
 21 void init(int s,int t){
 22     k[num].s=s;k[num].t=t;
 23     k[num].next=adj[s];adj[s]=num++;
 24     k[num].s=t;k[num].t=s;
 25     k[num].next=adj[t];adj[t]=num++;
 26 }
 27 int fa[300000],dp[300000],son[300000],size[300000];
 28 void Dfs1(int x){
 29     son[x]=0;size[x]=1;
 30     for(int i=adj[x];i!=-1;i=k[i].next){
 31         int o=k[i].t;
 32         if(o!=fa[x]){
 33             fa[o]=x;dp[o]=dp[x]+1;
 34             Dfs1(o);
 35             size[x]+=size[o];
 36             if(size[son[x]]<size[o])
 37                 son[x]=o;
 38         }
 39     }
 40 }
 41 int pos[300000],id[300000],top[300000],qiong[300000],cnt;
 42 void Dfs2(int u,int tp){
 43     pos[++cnt]=u;id[u]=cnt;
 44     top[u]=tp;
 45     if(son[u]) Dfs2(son[u],tp);
 46     for(int i=adj[u];i!=-1;i=k[i].next){
 47         int o=k[i].t;
 48         if(o!=fa[u]&&o!=son[u])
 49             Dfs2(o,o);
 50     }
 51     qiong[u]=cnt;
 52 }
 53 int LCA(int x,int y){
 54     int fx=top[x],fy=top[y];
 55     while(fx^fy){
 56         if(dp[fx]<dp[fy]){
 57             swap(fx,fy);
 58             swap(x,y);
 59         }
 60         x=fa[fx];fx=top[x];
 61     }
 62     return dp[x]<dp[y]?x:y;
 63 }
 64 int sum[7700000];
 65 void change(int x,int z,int l,int r,int &now){
 66     if(!x) return;
 67     if(!now) now=++dex;
 68     sum[now]+=z;
 69     if(l==r) return;
 70     int mid=(l+r)>>1;
 71     if(x<=mid) change(x,z,l,mid,lc[now]);
 72     else change(x,z,mid+1,r,rc[now]);
 73 }
 74 int query(int s,int t,int l,int r,int rt){
 75     if(!rt) return 0;
 76     if(s<=l&&r<=t) return sum[rt];
 77     int mid=(l+r)>>1;
 78     int ans=0;
 79     if(s<=mid) ans+=query(s,t,l,mid,lc[rt]);
 80     if(t>mid) ans+=query(s,t,mid+1,r,rc[rt]);
 81     return ans;
 82 }
 83 void clear(){
 84     dex=0;
 85     memset(lc,0,sizeof(lc));
 86     memset(rc,0,sizeof(rc));
 87     memset(sum,0,sizeof(sum));
 88     memset(rt,0,sizeof(rt));
 89 }
 90 int ans[300000];
 91 int main(){
 92     //freopen("runninga.in","r",stdin);
 93     //freopen("runninga.out","w",stdout);
 94     memset(adj,-1,sizeof(adj));
 95     n=read();m=read();
 96     for(int i=1;i<n;++i){
 97         x=read();y=read();
 98         init(x,y);
 99     }
100     for(int i=1;i<=n;++i)
101         w[i]=read();
102     Dfs1(1);Dfs2(1,1);
103     for(int i=1;i<=m;++i)
104         l[i].s=read(),l[i].t=read(),l[i].lca=LCA(l[i].s,l[i].t);
105     for(int i=1;i<=m;++i){
106         int now=dp[l[i].s];
107         change(id[l[i].s],1,1,n,rt[now]);
108         change(id[fa[l[i].lca]],-1,1,n,rt[now]);
109     }
110     for(int i=1;i<=n;++i)
111         ans[i]=query(id[i],qiong[i],1,n,rt[dp[i]+w[i]]);
112     clear();
113     for(int i=1;i<=m;++i){
114         int now=dp[l[i].s]-2*dp[l[i].lca]+2*n;
115         change(id[l[i].t],1,1,n,rt[now]);
116         change(id[l[i].lca],-1,1,n,rt[now]);
117     }
118     for(int i=1;i<=n;++i)
119         ans[i]+=query(id[i],qiong[i],1,n,rt[w[i]-dp[i]+2*n]);
120     for(int i=1;i<=n;++i)
121         printf("%d ",ans[i]);
122     return 0;
123 }
时间: 2024-10-14 09:05:44

NOIP2016天天爱跑步的相关文章

bzoj4719[Noip2016]天天爱跑步

Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.?天天爱跑步?是一个养成类游戏,需要玩家每天按时上线,完成打卡任务.这个游戏的地图可以看作一一棵包含 N个结点和N-1 条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达.树上结点编号为从1到N的连续正整数.现在有个玩家,第个玩家的起点为Si ,终点为Ti  .每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发, 以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去, 跑到

bzoj 4719: [Noip2016]天天爱跑步

Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.?天天爱跑步?是一个养成类游戏,需要 玩家每天按时上线,完成打卡任务.这个游戏的地图可以看作一一棵包含 N个结点和N-1 条边的树, 每条边连接两 个结点,且任意两个结点存在一条路径互相可达.树上结点编号为从1到N的连续正整数.现在有个玩家,第个玩家的 起点为Si ,终点为Ti  .每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发, 以每秒跑一条边的速度, 不间断地沿着最短路径向着自己的终点跑去

LCA+线段树 NOIP2016 天天爱跑步

天天爱跑步 题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.?天天爱跑步?是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏的地图可以看作一一棵包含 nnn个结点和 n?1n-1n?1条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达.树上结点编号为从111到nnn的连续正整数. 现在有mmm个玩家,第iii个玩家的起点为 SiS_iS?i??,终点为 TiT_iT?i?? .每天打卡任务开始时,所有玩家在第000秒同时从自己的起点出

bzoj4719: [Noip2016]天天爱跑步 树上差分

Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.?天天爱跑步?是一个养成类游戏,需要 玩家每天按时上线,完成打卡任务.这个游戏的地图可以看作一一棵包含 N个结点和N-1 条边的树, 每条边连接两 个结点,且任意两个结点存在一条路径互相可达.树上结点编号为从1到N的连续正整数.现在有个玩家,第个玩家的 起点为Si ,终点为Ti .每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发, 以每秒跑一条边的速度, 不间断地沿着最短路径向着自己的终点跑去,

noip2016 天天爱跑步

分析:这道题真心烦啊,是我做过noip真题中难度最高的一道了,到今天为止才把noip2016的坑给填满.暴力的话前60分应该是可以拿满的,后40分还是很有难度的. 定义:每个人的起点.终点:s,t;深度:deep[i];观察员出现时间:w[i]; 首先,树上两个点的最短路径肯定要经过LCA,那么对于路径x ---> y我们可以分成两部分:1.x ---> lca. 2.lca ---> y.先分析第一段路上的观察员i,显然s到i的距离等于w[i]才行,这段路是从x向上跳的,所以可以得到式

NOIP2016 天天爱跑步 正解

暴力移步 http://www.cnblogs.com/TheRoadToTheGold/p/6673430.html 首先解决本题应用的知识点: dfs序——将求子树的信息(树形)转化为求一段连续区间信息(线形) 线段树——求区间信息 树上差分——统计答案 lca——拆分路径 树链剖分——求lca 另deep[]表示节点的深度,watch[]表示观察者的出现时间,s表示玩家起点,t表示终点 固定节点的观察者出现的时间固定,说明对这个观察者有贡献的点是有限且固定的 只有满足  观察者出现时间=玩

BZOJ 4719 [Noip2016]天天爱跑步 ——树链剖分

一直以为自己当时是TLE了,但是再看发现居然WA? 然后把数组扩大一倍,就A掉了.QaQ 没什么好说的.一段路径分成两段考虑,上升的一段深度+时间是定值,下降的一段深度-时间是定值,然后打标记统计即可. 发现大概是统计数组因为深度+时间太大炸掉了. 现在想想,当时没有对拍,真是后怕. #include <cstdio> #include <vector> #include <cstring> #include <iostream> #include <

BZOJ 4719--天天爱跑步(LCA&amp;差分)

4719: [Noip2016]天天爱跑步 Time Limit: 40 Sec  Memory Limit: 512 MBSubmit: 1464  Solved: 490[Submit][Status][Discuss] Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.?天天爱跑步?是一个养成类游戏,需要 玩家每天按时上线,完成打卡任务.这个游戏的地图可以看作一一棵包含 N个结点和N-1 条边的树, 每条边连接两 个结点,且任意两个结点存在一条路

天天爱跑步[NOIP2016]

时间限制:2 s   内存限制:512 MB [题目描述] 小C同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏的地图可以看作一棵包含n个结点和n-1条边的树,每条边连接两个结点,且任意两个结点存在一条路径互相可达.树上结点编号为从1到n的连续正整数. 现在有m个玩家,第i个玩家的起点为Si,终点为Ti.每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发,以每秒跑一条边的速度,不间断