真正的危机不是机器人像人一样思考,而是人像机器一样思考。 ——凉宫春日的忧郁

[NOIP2016]天天爱跑步

[NOIP2016]天天爱跑步

题目

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

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

   现在有m个玩家,第i个玩家的起点为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

INPUT1

6 3

2 3

1 2

1 4

4 5

4 6

0 2 5 1 2 3

1 5

1 3 

2 6

OUTPUT1

2 0 0 1 1 1

INPUT2

5 3

1 2

2 3

2 4

1 5

0 1 0 3 0

3 1

1 4

5 5

OUTPUT2

1 2 1 0 1

范围

解题报告

从今天下午做到晚上,终于A了= =(明明是从去年做到现在吧啊喂)

设$deep[]$表示节点的深度,$watch[]$表示观察者的出现时间,$s$表示玩家起点,$t$表示终点

首先,只有满足 观察者出现时间=玩家起点与观察者距离  的玩家才对观察者有贡献

显然,在树上的问题,我们很容易想到把路径拆成从起点向上跑到$lca$的路径和$lca$的子节点向下跑到终点的两条路径

对于向上跑的,如果玩家能被观察者$i$观察到,那么一定满足:

$$deep[s]-deep[i]=watch[i]$$

对于向下跑的,如果玩家能被观察者$i$观察到,那么一定满足:

$$deep[s]+deep[i]-2\times deep[lca(s,i)]=watch[i]$$

(显然这两个式子都是由“观察者出现时间=玩家起点与观察者距离”推出来的)

等号左边是玩家起点与观察者的距离,等号右边是观察者出现时间

 

我们先分析向上跑的——路径:从起点向上跑到$lca$

将式子移项可以得到:

$$deep[s]=deep[i]+watch[i]$$

我们知道$deep[i]$与$watch[i]$都是定值,也就是说,对于观察者$i$,他能观察到的向上跑的玩家,是所有起点=$deep[i]+watch[i]$的玩家

而向上跑还能经过$i$的,一定在$i$的子树中

所以,我们知道,以$i$为根的子树中,所以深度为$deep[i]+watch[i]$的玩家都能被$i$观察到

那么我们搞一个$dfs$序,就能把树上的问题转化为区间上的问题了

深度怎么处理呢?

我们对每一层深度建一棵线段树(此时我们需要动态开点(第一棵动态开点线段树,快打死我了)),那么剩下的就很好解决了

我们将问题转化为了在深度为$deep[i]+watch[i]$的线段树中,查询区间$[a,b]$的玩家个数

玩家个数自然很好处理,瞎XX差分一下就可以了

差分:

在起点处$+1$

在$lca$父节点处$-1$

为什么?

这是差分的惯用思想(科普差分):

我们考虑一个大区间,一开始我们全赋为0,然后我们插入一个区间,将左端点$+1$,右端点后面一个点$-1$,那么在该区间前面的点,显然不受影响,在该区间中的点,我们进行求前缀和处理,我们只能求到左端点的$+1$,而不会求到右端点的$-1$,所以该区间可以被算上,在该区间后的点,我们再求一下前缀和,左端点的$+1$与右端点的$-1$相抵消,对其没有影响(在计算时,它也不会被算在该区间里)

从$lca$的子节点向下跑到终点的同理

把式子移项可得:

$$deep[s]-2\times deep[lca(s,i)]=watch[i]-deep[i]$$

然后$watch[i]$和$deep[i]$仍然是定值,所以我们在深度为$deep[s]-2\times deep[lca(s,i)]$的线段树里进行差分,终点处$+1$,$lca$处$-1$

查询深度为$watch[i]-deep[i]$的线段树即可

-----update-----

 动态开点线段树:

注意到,我们在每个深度都开了一棵线段树,所以,我们不可能像以前一样,直接开一大圈线段树,那样内存一定会炸,所以我们可以先开出足够的数组,然后,当我们需要一棵新的线段树时,只需要增加一个新的根节点即可,同样的,当我们需要一个新节点时,我们不需要像以前打线段树一样,去找父节点编号$\times 2$什么的,我们直接在最后开出的节点后面加一个节点,并将它的父节点的儿子指针指向该节点,这样,就可以省去那些一言不合就$\times 2$的下标了,同样,也可以省去许多会让你$MLE$的内存

-----update over-----

细节处理:

  1. 做完向上跑的玩家后,清空线段树
  2. 向下跑的$deep[s]-2\times deep[lca(s,i)]$可能出负数,所以全体后移,数组大小要开大一些
  3. 不会动态开点的先学习一下,不要像我一样作死的以为随便打打就好了= =
  1 #include<iostream>
  2 #include<cstring>
  3 #include<cstdio>
  4 using namespace std;
  5 inline int read(){
  6     int sum(0);
  7     char ch(getchar());
  8     for(;ch<'0'||ch>'9';ch=getchar());
  9     for(;ch>='0'&&ch<='9';sum=sum*10+(ch^48),ch=getchar());
 10     return sum;
 11 }
 12 struct edge{
 13     int e;
 14     edge *n;
 15     edge():e(0),n(NULL){}
 16 }a[600005],*pre[300005];
 17 int tot;
 18 inline void insert(int s,int e){
 19     a[++tot].e=e;
 20     a[tot].n=pre[s];
 21     pre[s]=&a[tot];
 22 }
 23 struct que{
 24     int s,e,lca;
 25 }peo[300005];
 26 int n,m;
 27 int cnt;
 28 int dep[300005],fa[20][300005],id[300005],out[300005];
 29 inline void dfs(int u){
 30     id[u]=++cnt;
 31     for(int i=1;(1<<i)<=dep[u];++i)
 32         fa[i][u]=fa[i-1][fa[i-1][u]];
 33     for(edge *i=pre[u];i;i=i->n){
 34         int e(i->e);
 35         if(e!=fa[0][u]){
 36             fa[0][e]=u;
 37             dep[e]=dep[u]+1;
 38             dfs(e);
 39         }
 40     }
 41     out[u]=cnt;
 42 }
 43 inline int lca(int x,int y){
 44     if(dep[x]<dep[y])
 45         swap(x,y);
 46     int delta(dep[x]-dep[y]);
 47     for(int i=0;delta>0;++i)
 48         if((1<<i)&delta){
 49             delta^=1<<i;
 50             x=fa[i][x];
 51         }
 52     if(x==y)
 53         return x;
 54     for(int i=19;i>=0;--i)
 55         if(fa[i][x]!=fa[i][y])
 56             x=fa[i][x],y=fa[i][y];
 57     return fa[0][x];
 58 }
 59 int dfn;
 60 int sum[7500005],lc[7500005],rc[7500005],root[1200005];
 61 inline void update(int l,int r,int pos,int w,int &now){
 62     if(!pos)
 63         return;
 64     if(!now)
 65         now=++dfn;
 66     sum[now]+=w;
 67     if(l==r)
 68         return;
 69     int mid((l+r)>>1);
 70     if(pos<=mid)
 71         update(l,mid,pos,w,lc[now]);
 72     else
 73         update(mid+1,r,pos,w,rc[now]);
 74 }
 75 inline int query(int l,int r,int ll,int rr,int now){
 76     if(!now)
 77         return 0;
 78     if(ll<=l&&r<=rr)
 79         return sum[now];
 80     int mid((l+r)>>1);
 81     int ret(0);
 82     if(rr<=mid)
 83         return query(l,mid,ll,rr,lc[now]);
 84     else
 85         if(ll>mid)
 86             return query(mid+1,r,ll,rr,rc[now]);
 87         else
 88             return query(l,mid,ll,mid,lc[now])+query(mid+1,r,mid+1,rr,rc[now]);
 89 /*    if(ll<=mid)
 90         ret+=query(l,mid,ll,rr,lc[now]);
 91     if(mid<rr)
 92         ret+=query(mid+1,r,ll,rr,rc[now]);*/
 93 }
 94 inline void clear(){
 95     dfn=0;
 96     memset(lc,0,sizeof(lc));
 97     memset(rc,0,sizeof(rc));
 98     memset(sum,0,sizeof(sum));
 99     memset(root,0,sizeof(root));
100 }
101 int ans[300005];
102 int w[300005];
103 inline int gg(){
104     freopen("runninga.in","r",stdin);
105     freopen("runninga.out","w",stdout);
106     memset(pre,NULL,sizeof(pre));
107     n=read(),m=read();
108     for(int i=1;i<n;++i){
109         int x(read()),y(read());
110         insert(x,y),insert(y,x);
111     }
112     dfs(1);
113     for(int i=1;i<=n;++i)
114         w[i]=read();
115     for(int i=1;i<=m;++i)
116         peo[i].s=read(),peo[i].e=read(),peo[i].lca=lca(peo[i].s,peo[i].e);
117     for(int i=1;i<=m;++i){
118         int de(dep[peo[i].s]);
119         update(1,n,id[peo[i].s],1,root[de]);
120         update(1,n,id[fa[0][peo[i].lca]],-1,root[de]);
121     }
122     for(int i=1;i<=n;++i)
123         ans[i]+=query(1,n,id[i],out[i],root[dep[i]+w[i]]);
124     clear();
125     for(int i=1;i<=m;++i){
126         int de(dep[peo[i].s]-(dep[peo[i].lca]<<1)+(n<<1));
127         update(1,n,id[peo[i].e],1,root[de]);
128         update(1,n,id[peo[i].lca],-1,root[de]);
129     }
130     for(int i=1;i<=n;++i)
131         ans[i]+=query(1,n,id[i],out[i],root[w[i]-dep[i]+(n<<1)]);
132     for(int i=1;i<=n;++i)
133         printf("%d ",ans[i]);
134     return 0;
135 }
136 int K(gg());
137 int main(){;}
View Code

花絮?

我:哇,为什么全WA了

同桌:哪有全WA,你看还有15个T呢

我:去去去,我从去年做到现在,去年还能拿点分,为啥现在连分都拿不了了

同桌:哇,我A了5个点

我:剩下的呢

同桌:全T了

我:快拿cena跑一跑

同桌:哇,我A了

我:多快?

同桌:100秒= =

(一下午就这样过去了)

我:哇,我A了,你还差多少

同桌:还差一点

(几分钟过去了)

我:哇,你要A了

同桌:咦,怎么WA了一个

我:是不是数组开小了

同桌:唔,好像是,我回去开大点

(又是几分钟过去了)

我:A了?

同桌:嗯

我:咋WA的

同桌:数组开小了

我:那咋WA那么多次

同桌:我不知道动态开点咋搞啊QAQ

然后就有了上面的细节处理= =

posted @ 2017-08-13 20:41  Hzoi_Mafia  阅读(527)  评论(4编辑  收藏  举报
我们都在命运之湖上荡舟划桨,波浪起伏着而我们无法逃脱孤航。但是假使我们迷失了方向,波浪将指引我们穿越另一天的曙光。 ——死神