1. 程式人生 > >【題解】BZOJ4719:[Noip2016]天天愛跑步

【題解】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.

相關推薦

題解BZOJ4719:[Noip2016]天天跑步

首先膜拜一下大牛 感謝大佬的這篇題解 Description 小c同學認為跑步非常有趣,於是決定製作一款叫做《天天愛跑步》的遊戲。?天天愛跑步?是一個養成類遊戲,需要 玩家每天按時上線,完成打卡任務。這個遊戲的地圖可以看作一一棵包含 N個結點和N-1 條

[樹鏈剖分+線段樹] bzoj4719: [Noip2016]天天跑步留坑待填

從s到t 上升時,對於經過的每一個節點i 設經過了t[i]條路徑 即用時t[i] t[i]=dep[s]-dep[i] 得t[i]+dep[i]=dep[s] 同樣 下降時,對於每一個經過的節點i t[i]=(dep[i]-dep[lc

bzoj4719: [Noip2016]天天跑步 樹上差分

-a 用兩個 output 玩家 name cst while con bzoj Description 小c同學認為跑步非常有趣,於是決定制作一款叫做《天天愛跑步》的遊戲。?天天愛跑步?是一個養成類遊戲,需要 玩家每天按時上線,完成打卡任務。這個遊戲的地圖可以看作一一棵包

bzoj4719 [Noip2016]天天跑步(樹+lca+樹上差分+思路題)

進入bzoj法眼的noip(lus)題hh。題解太麻煩啦。。。不寫啦。。。就體會一下思想:要是對於每條路徑操作,看他會影響哪些點的貢獻,鐵鐵的會t。所以考慮怎樣的一條路徑會對一個點產生貢獻,先討論簡化版的鏈的情況,發現要討論左右。結合另外兩個部分分,感覺就是把路

bzoj4719: [Noip2016]天天跑步

題目 題解 #include<bits/stdc++.h> using namespace std; const int N=300003,M=600003; struct kk{ int u,v,lca,dis; }p[N]; st

NOIP2016天天跑步 題解報告lca+樹上統計(桶)

題目描述 小c同學認為跑步非常有趣,於是決定製作一款叫做《天天愛跑步》的遊戲。«天天愛跑步»是一個養成類遊戲,需要玩家每天按時上線,完成打卡任務。 這個遊戲的地圖可以看作一一棵包含 nn個結點和 n-1n−1條邊的樹, 每條邊連線兩個結點,且任意兩個結點存在一條

[bzoj4719][樹鏈剖分][Noip2016]天天跑步

4719: [Noip2016]天天愛跑步 Time Limit: 40 Sec Memory Limit: 512 MB Submit: 1022 Solved: 342 [Submit][Status][Discuss] Description

LCA+線段樹 NOIP2016 天天跑步

art swa 單獨 編譯 如果 建立 const 會有 void 天天愛跑步 題目描述 小c同學認為跑步非常有趣,於是決定制作一款叫做《天天愛跑步》的遊戲。?天天愛跑步?是一個養成類遊戲,需要玩家每天按時上線,完成打卡任務。 這個遊戲的地圖可以看作一一棵包含 nnn個結

noip2016 天天跑步

ini node 右移 += upd 時間 個人 方法 最短路 分析:這道題真心煩啊,是我做過noip真題中難度最高的一道了,到今天為止才把noip2016的坑給填滿.暴力的話前60分應該是可以拿滿的,後40分還是很有難度的. 定義:每個人的起點、終點:s,t;深度

[luogu1600 noip2016] 天天跑步 (樹上差分)

分享圖片 etc tdi 制作 name cout gist clas 輸出 題目描述 小c同學認為跑步非常有趣,於是決定制作一款叫做《天天愛跑步》的遊戲。《天天愛跑步》是一個養成類遊戲,需要玩家每天按時上線,完成打卡任務。 這個遊戲的地圖可以看作一一棵包含 n個結點和 n

[NOIP2016] 天天跑步

多少 避免 ont find ans 貢獻 noi int bool ~~~題面~~~ 題解: 很久以前就想寫了,一直沒敢做,,,不過今天寫完沒怎麽調就過了還是很開心的。 首先我們觀察到跑步的人數是很多的,要一條一條的遍歷顯然是無法承受的,因此我們要考慮更加優美的方法

NOIP2016 天天跑步(線段樹/桶)

短路徑 就會 遊戲 結束 else 決定 for class 正整數 題目描述 小c同學認為跑步非常有趣,於是決定制作一款叫做《天天愛跑步》的遊戲。天天愛跑步是一個養成類遊戲,需要 玩家每天按時上線,完成打卡任務。 這個遊戲的地圖可以看作一一棵包含 N個結點和N-1 條

[Noip2016]天天跑步 樹上差分

stact 0ms 情況下 類型 輸出 記錄 一行 push pri $ \rightarrow $ 戳我進洛谷原題** $ \rightarrow $ 戳我進BZOJ原題** 天天愛跑步   時空限制 \quad 2000ms / 512MB 題目描述 小c同學認為跑步非

NOIP2016 天天跑步 (樹上差分+dfs)

產生 using cto continue 有一個 swa http get bug 題目大意:給你一顆樹,樹上每個點都有一個觀察員,他們僅會在 w[i] 時刻出現,觀察正在跑步的玩家 一共有m個玩家,他們分別從節點 s[i] 同時出發,以每秒跑一條邊的速度,沿著到 t[i

4719: [Noip2016]天天跑步

Time Limit: 40 Sec Memory Limit: 512 MB Submit: 1986 Solved: 752 [Submit][Status][Discuss] Description 小c同學認為跑步非常有趣,於是決定製作一款叫做《天天愛跑步》的遊戲。?天天愛跑步?是一個養成類遊戲,

題解LuoGu2831/noip2016:憤怒的小鳥

原題傳送門 【題解】 我先想到了dfs,估計了一下時間複雜度,估不出來,覺得懸,就“瞟了一眼”題解,發現第一篇就是dfs,然後信心滿滿的碼好,莫名wa掉,找了很長時間錯也沒找出來。 然後我又想起一年前某位老師講狀壓dp的時候講過這道題,就改用了狀壓dp dp[i

NOIP2016天天跑步——LCA+樹上差分

Description 小c同學認為跑步非常有趣,於是決定製作一款叫做《天天愛跑步》的遊戲。?天天愛跑步?是一個養成類遊戲,需要玩家每天按時上線,完成打卡任務。這個遊戲的地圖可以看作一一棵包含 N個結點和N-1 條邊的樹, 每條邊連線兩個結點,且任意兩個結點存

NOIP2016天天跑步

題目在這 題目描述 小c同學認為跑步非常有趣,於是決定製作一款叫做《天天愛跑步》的遊戲。«天天愛跑步»是一個養成類遊戲,需要玩家每天按時上線,完成打卡任務。 這個遊戲的地圖可以看作一一棵包含 nnn個結點和 n−1n-1n−1條邊的樹, 每條邊連線兩個結點,且任意兩個結

[NOIP2016]天天跑步

題目大意 給定一個n個節點,n−1條邊的樹。有m個玩家,第i個玩家從xi走樹上最短路徑到yi。玩家第0秒在自己的起點上,然後每秒移動一條邊,移動到終點後結束移動。 每個節點上有一個觀察員以及權值wi。如果有一個玩家在其移動的第wi秒恰好到達這個點,那麼這個點

[Noip2016]天天跑步 LCA+樹上差分

—————————————- 概述 題目大意如下: 給定一棵n個點的樹,每一個點有一個點權w[i],再告訴你m條路徑的起點u和終點v。對於一條路徑,假如路徑上某一點x到起點u的距離等於w[x],那麼x的貢獻加1。最後求每一個點的貢獻。(1≤n,m≤30