首页 > 其他分享 >NOIP2016Day1T2-天天爱跑步

NOIP2016Day1T2-天天爱跑步

时间:2022-11-22 18:31:51浏览次数:55  
标签:结点 end watch deep 玩家 天天 跑步 lca NOIP2016Day1T2


2、天天爱跑步

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

题目描述

小C同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。

 

《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。

 

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

 

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

 

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

 

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

 

输入

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

 

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

 

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

 

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

 

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

 

输出

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

 

样例输入

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



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

样例输出

2 0 0 1 1 1


1 2 1 0 1

提示

 

【样例 1 说明】

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

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

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

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

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

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

【子任务】

每个测试点的数据规模及特点如下表所示。提示:数据范围的个位上的数字可以帮助判断是哪一种数据类型。

 

 

测试点编号

n

m

约定

1

 

=991

 

=991

所有人的起点等于自己的终点,

Si =Ti

2

3

 

=992

 

=992

Wj =0

4

5

=993

=993

6

 

=99994

 

=99994

树退化成一条链,其中1与2有边,

2与3有边,...,n−1与n有边

7

8

9

 

 

=99995

 

 

=99995

 

所有的Si =1

10

11

12

13

 

 

=99996

 

 

=99996

 

所有的Ti =1

14

15

16

17

 

=99997

 

=99997

 

18

19

20

=299998

=299998 

 

用了2、3个小时,才把这道题弄出来……

watch[i]表示观察员出现的时间。deep[i]表示节点的深度。
我们可以先用tarjan求出lca。用树上差分统计答案。

然后把每条路径拆成 起点到lca(向上跑)和终点到lca的子节点(向下跑)的两条路径。

对于向上跑的,如果玩家能被观察员i观察到,那么deep[s]-deep[i]=watch[i]   式①

对于向下跑的,就是 deep[s]+deep[i]-2*deep[lca(s,i)]=watch[i]  式②

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

向上跑的很显然,向下跑的如何理解?

假设我们知道点a,b到lca(a,b)的距离分别为da,db,那么a,b之间的距离=da+db

但这里的deep不是到lca的距离,是深度,即到根节点的距离+1

deep[s]+deep[i]包含2段信息,1、s到i的距离,   2、lca(s,i)到根节点的距离+1 

第2段包含了2次,所以减去

先看向上跑的

玩家路径:玩家起点 到 起点与终点的lca

将式①移项,deep[s]=deep[i]+watch[i]

发现等号右边是定值

也就是说对与观察者i,他所能观察到的向上跑的玩家,是所有的起点=deep[i]+watch[i]的玩家

换句话说,以i为根的子树中,所有深度为deep[i]+watch[i]的玩家都能被i观察到

我们如果搞一个dfs序,i的在a时入栈,在b时出栈,

那么以i为根的子树就可以转化为区间[a,b]

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

现在就差玩家个数了

很容易想到在起点处+1

但是还要在起点与终点的lca的父节点处-1

差分惯用思想

用sum[]统计这些1和-1的和

那么问题就转化为了  在深度为deep[i]+watch[i]的线段树中,查询区间[a,b]的sum和

提问:为什么是起点处+1,lca的父节点处-1,可以反过来吗?

不可以。

因为起点的深度深,lca的父节点深度浅,在深度深的节点处+1,以深度深度浅的点为根的子树可以包含这个点

想想序列上的差分,是左端点+1,右端点后面的点-1

因为序列差分与前缀和相联系,前面的点的信息对后面的点会产生影响,所以只需加一个1

这里查询的是子树信息,是这个点深度及以下的信息

对照理解即可

向下跑的同理,只简单说怎么做

玩家路径:lca的子节点到玩家终点

把式②移项 deep[s]-2*deep[lca(s,i)]=watch[i]-deep[i]

在watch[i]-deep[i]深度为deep[s]-2*deep[lca(s,i)]的线段树中,终点处+1,lca处-1

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

2个小问题:

1、做完向上跑的后,不要忘了清空线段树

2、向下跑的deep[s]-2*deep[lca(s,i)]可能会产生负数,所以全体后移一定长度,root[]数组开大

Code:

 

var
n,m,x,y,tot,tot1,tlca,i,j,k:longint;
last,lca,last1,father,watch,deep,dis,plca,lalca,ans:array[0..300010] of longint;
vis:array[0..300010] of boolean;
pre,other,pre1,other1:array[0..600010] of longint;
a,b:array[-400005..400005] of longint;
procedure add(a,b:longint);
begin
inc(tot);
pre[tot]:=last[a];
last[a]:=tot;
other[tot]:=b;
end;
procedure add1(a,b:longint);
begin
inc(tot1);
pre1[tot1]:=last1[a];
last1[a]:=tot1;
other1[tot1]:=b;
end;
procedure addlca(c:longint);
begin
inc(tlca);
plca[tlca]:=lalca[c];
lalca[c]:=tlca;
end;
function getfather(x:longint):longint;inline;
begin
if x=father[x] then exit(x);
father[x]:=getfather(father[x]);
exit(father[x]);
end;
procedure tarjan(x:longint);
var p,r:longint;
begin
vis[x]:=true;
p:=last[x];
while p>0 do
begin
r:=other[p];
if not vis[r] then
begin
deep[r]:=deep[x]+1;
tarjan(r);
father[r]:=x;
end;
p:=pre[p];
end;
p:=last1[x];
while p>0 do
begin
r:=other1[p];
if vis[r] then lca[p>>1]:=getfather(r);
p:=pre1[p];
end;
end;
procedure dfs(x,sa,sb:longint);
var p,r:longint;
begin
vis[x]:=true;
p:=last1[x];
while p>0 do
begin
if p and 1=0 then inc(a[deep[x]]) else
inc(b[dis[p>>1]-deep[x]]);
p:=pre1[p];
end;
p:=last[x];
while p>0 do
begin
r:=other[p];
if not vis[r] then
dfs(r,a[watch[r]+deep[r]],b[watch[r]-deep[r]]);
p:=pre[p];
end;
ans[x]:=a[watch[x]+deep[x]]+b[watch[x]-deep[x]]-sa-sb;
p:=lalca[x];
while p>0 do
begin
dec(a[deep[other1[p<<1 or 1]]]);
dec(b[dis[p]-deep[other1[p<<1]]]);
if deep[other1[p<<1 or 1]]-deep[x]=watch[x] then dec(ans[x]);
p:=plca[p];
end;
end;
begin
readln(n,m);
for i:=1 to n-1 do
begin
readln(x,y);add(x,y);add(y,x);
end;
for i:=1 to n do read(watch[i]);
readln;tot1:=1;
for i:=1 to m do
begin
readln(x,y);add1(x,y);add1(y,x);
end;
for i:=1 to n do father[i]:=i;
tarjan(1);
for i:=1 to m do
dis[i]:=deep[other1[i<<1]]+deep[other1[i<<1 or 1]]-deep[lca[i]]*2;
for i:=1 to m do addlca(lca[i]);
fillchar(vis,sizeof(vis),0);
dfs(1,0,0);
for i:=1 to n-1 do write(ans[i],' ');writeln(ans[n]);
end.

 

标签:结点,end,watch,deep,玩家,天天,跑步,lca,NOIP2016Day1T2
From: https://blog.51cto.com/u_15888102/5878334

相关文章