【题解】BZOJ4719:[Noip2016]天天爱跑步

首先膜拜一下大牛
感谢大佬的这篇题解

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.

猜你喜欢

转载自blog.csdn.net/ModestCoder_/article/details/81393800