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 个整数,其中第个整数为Wj , 表示结点出现观察员的时间。
接下来 M行,每行两个整数Si和Ti,表示一个玩家的起点和终点。
对于所有的数据,保证 。
1<=Si,Ti<=N,0<=Wj<=N
Output
输出1行N 个整数,第个整数表示结点的观察员可以观察到多少人。
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号点,玩家1被观察到,共1人被观察到。
对于6号点,玩家3被观察到,共1人被观察到。
【题解】
LCA+桶+差分
首先想到的是暴力算法:跑每个玩家经过的路径,在每个节点观察,统计。
我们在暴力算法上进行改进
- 发现:暴力过程中,有许多到达的点,并不对最终的答案产生贡献,是无用、重复的点
- 期望:把无用的点去掉,也就是不到达。则算法变成暴力跑有用的点,还是会Tle
- 探索:对于当前一个结点u,要观察到从v出发的玩家,要满足的条件是:dis(u,v)=w[u]
- 期望:那么则在每个点,期望可以实现O(1)得到其他结点对自己的贡献
- 发现:有付出才有回报,什么都不做,O(1)完成这个操作岂不是不劳而获?那么出现新问题:如何维护在每个点可以看到的玩家数
现在的问题:如何有效跑点,以及如何维护在每个点可以看到的玩家数才能不Tle?
针对dis(u,v)=w[u]这个公式做文章
我们发现在树上,往上走与往下走是不同的。对于每条路s->t,因为走的是最短路,所以把s->t拆分成s->lca(s,t)、lca(s,t)->t,分两类考虑。我用d[u]表示u的深度,若有两点u、v,满足v为起点,若u要观察到v
- s->lca(s,t)中需满足d[u]+w[u]=d[v]
- lca(s,t)->t 中需满足d[u]-w[u]=d[v]
如此一来,每个点只需要跑2*2次:遍历时1次,回溯时1次。s->lca(s,t)从根节点开始跑,lca(s,t)->t也从根节点开始跑。
接下来思考维护在每个点可以看到的玩家数。
我们可以开一个桶,针对d[u]+w[u]=d[v],w[u]-w[u]=d[v]对桶下定义:vector[deep]表示深度为deep做起点的个数。比如:有一个节点3,深度为2,有玩家3->6,3->8,那么vector[2]=2,这样的话,每次答案的累积就是当前结点u看到的v的vector的变化量
我们在这个定义中发现了诸多问题:
Q1:深度为deep的点有多个
Q2:起点为v的玩家有多个,但终点不同(即在u为终点结束的玩家不能参与对其子树的贡献统计)
针对上述两个问题,我们就需要做一个删桶操作,把已经做不了贡献的路径删除
那么,在每个结点统计出要被删除的路径,需要O(玩家数),有出现许多重复、无用的路径浪费时间!!!
思考:如何每次只删与当前有关的桶
解决:推出打标记操作:在当前结点,给它的终点(转弯)打标记;删掉自己子树中给自己打的标记
这是差分思想
那么方法到这里已经完整了,
dfs的结构:
对于dfs到的每个点,我们需要
- 往下跑
- 加桶,加标记
- 累计自己的ans
- 删桶,删标记
往上走与往下走还有很大的区别:往上走的过程中是当前结点的子树影响自己的答案;而往下走的过程中是当前结点影响子树的答案
往上走的过程是常规编法,但往下走的过程中,我们为了不破坏dfs的结构,想到一个“终点作起点”的方法,我用len[i]记录第i个玩家的总路程,那么对于第i条路,起点为s,终点为t,有d[s]=d[t]-len[i],每次加桶时++vector[d[u]-len[number]]
我们又发现一个问题,这样的话,lca(s,t)算了两次,但其实只能算一次,所以我们在往上走的过程中(也可以是往下走)的3,4两步交换位置。即一次是先删再统计,一次是先统计再删(方法来自开头膜拜的chy大牛)。
分析结束了,我自认为讲的挺详细的~~~,若没看懂还有代码
Code:
uses math;
const
maxn=300010;
type Node1=record
v,next:longint;
end;
Node2=record
lca,dis:longint;
end;
var
edge1,edge2,edge3,edge4:array[0..maxn*2] of Node1;
len:array[0..maxn*2] of Node2;
head1,head2,head3,head4,w,d:array[0..maxn*2] of longint;
vector,ans:array[-maxn*2..maxn*2] of longint;
fa:array[0..maxn,0..30] of longint;
n,m,x,y,i,num1,num2,num3,num4,maxdeep:longint;
procedure add1(x,y:longint);
begin
inc(num1);
edge1[num1].v:=y;
edge1[num1].next:=head1[x];
head1[x]:=num1;
end;
procedure add2(x,y:longint);
begin
inc(num2);
edge2[num2].v:=y;
edge2[num2].next:=head2[x];
head2[x]:=num2;
end;
procedure add3(x,y:longint);
begin
inc(num3);
edge3[num3].v:=y;
edge3[num3].next:=head3[x];
head3[x]:=num3;
end;
procedure add4(x,y:longint);
begin
inc(num4);
edge4[num4].v:=y;
edge4[num4].next:=head4[x];
head4[x]:=num4;
end;
procedure build(u,pre:longint); //建树
var
i,e:longint;
begin
d[u]:=d[pre]+1; fa[u][0]:=pre; maxdeep:=max(maxdeep,d[u]);
i:=0;
while fa[u][i]>0 do
begin
fa[u][i+1]:=fa[fa[u][i]][i];
inc(i);
end;
i:=head1[u];
while i>0 do
begin
e:=edge1[i].v;
if e<>pre then build(e,u);
i:=edge1[i].next;
end;
end;
procedure swap(var x,y:longint);
var
tmp:longint;
begin
tmp:=x;
x:=y;
y:=tmp;
end;
function getlca(x,y:longint):longint; //倍增lca
var
i:longint;
begin
if d[x]>d[y] then swap(x,y);
for i:=20 downto 0 do if d[x]<=d[y]-(1<<i) then y:=fa[y][i];
if x=y then exit(x);
for i:=20 downto 0 do
if fa[x][i]<>fa[y][i] then
begin
x:=fa[x][i]; y:=fa[y][i];
end;
exit(fa[x][0]);
end;
procedure dfs1(u,pre:longint); //s->lca(s,t)
var
i,e,tmp:longint;
begin
if d[u]+w[u]<=maxdeep then tmp:=vector[d[u]+w[u]];
i:=head1[u];
while i>0 do
begin
e:=edge1[i].v;
if e<>pre then dfs1(e,u);
i:=edge1[i].next;
end;
i:=head3[u];
while i>0 do
begin
e:=edge3[i].v;
inc(vector[d[u]]);
add2(len[e].lca,d[u]);
i:=edge3[i].next;
end;
i:=head2[u];
while i>0 do
begin
e:=edge2[i].v;
dec(vector[e]);
i:=edge2[i].next;
end;
if (d[u]+w[u]<=maxdeep) then inc(ans[u],vector[d[u]+w[u]]-tmp);
end;
procedure dfs2(u,pre:longint); //lca(s,t)->t
var
i,e,tmp:longint;
begin
tmp:=vector[d[u]-w[u]];
i:=head1[u];
while i>0 do
begin
e:=edge1[i].v;
if e<>pre then dfs2(e,u);
i:=edge1[i].next;
end;
i:=head4[u];
while i>0 do
begin
e:=edge4[i].v;
inc(vector[d[u]-len[e].dis]);
add2(len[e].lca,d[u]-len[e].dis);
i:=edge4[i].next;
end;
inc(ans[u],vector[d[u]-w[u]]-tmp);
i:=head2[u];
while i>0 do
begin
e:=edge2[i].v;
dec(vector[e]);
i:=edge2[i].next;
end;
end;
begin
readln(n,m);
for i:=1 to n-1 do
begin
readln(x,y);
add1(x,y); add1(y,x);
end;
d[0]:=-1; //根节点深度为0
build(1,0);
for i:=1 to n do read(w[i]);
for i:=1 to m do
begin
readln(x,y);
add3(x,i); add4(y,i); //添加询问
len[i].lca:=getlca(x,y);//记录lca
len[i].dis:=d[x]+d[y]-2*d[len[i].lca];//记录距离
end;
dfs1(1,0);
num2:=0; //初始化不能忘
fillchar(vector,sizeof(vector),0);
fillchar(head2,sizeof(head2),0);
fillchar(edge2,sizeof(edge2),0);
dfs2(1,0);
for i:=1 to n do write(ans[i],' ');
end.