NOIp2016 天天愛跑步 樹上差分

2022-05-31 04:00:11 字數 4401 閱讀 4935

題目型別:lca+思維

傳送門:>here

<

題意:給出一棵樹,有\(m\)個人在這棵樹上跑步。每個人都從自己的起點\(s[i]\)跑到終點\(t[i]\),跑過一條邊的時間為1秒。現在每個節點都有乙個觀察員,節點\(i\)上的觀察員會在第\(w[i]\)秒進行觀察,如果有\(x\)個人此時到達節點\(i\),則這個觀察員能夠觀察到\(x\)個人。問所有人跑步結束以後每個觀察員可以觀察到多少人

這道題是公認所有\(noip\)中最難的一道題。但其實這道題的資料約定能夠給我們很大的提示。

其實這道題的正解就是對各個部分分的方法的彙總和整合。因此我們先一一分析部分分如何拿

由於\(n\)是\(1000\),可以暴力模擬每個人的行動軌跡。求乙個\(lca\)暴力走一下就好了,沒有思維難度。這個部分分能夠首先讓你熟悉題目的意思

乍一看很簡單,其實由於\(n=99994\),也不是那麼容易的。事實上,這乙個部分分是整道題的精髓。

由於樹已經退化為鏈,所以所有的路徑要麼從左往右,要麼從右往左。我們可以記錄乙個桶\(b[x]\)。當我們從左往右掃到\(i\)時,\(b[x]\)表示以\(x\)為起點的所有路徑中,能夠一直延伸到\(i\)的路徑數量。

記錄這個有什麼用呢?對於節點\(i\),由於路徑從左往右,所以如果想要一條路徑在走到\(i\)的時候被觀察到,那麼這條路徑的起點\(s\)一定滿足:$$s=i-w[i]$$因此我們在考慮節點\(i\)時,只需要考慮以\(i-w[i]\)為起點的能夠延伸到\(i\)的路徑條數,這些路徑一定能夠在\(i\)點被觀察到。因此我們需要統計的就是\(b[i-w[i]\)。那麼從右往左的路徑也類似,我們只需要統計\(b[i+w[i]]\)就可以了

那麼如何維護\(b\)陣列呢?我們可以首先記錄每個點的起點個數,然後過了終點以後減去終點對應的那些起點。例如\(2\)開頭只有\(3\)條路徑:\((2,4) \ (2,3) \ (2,7)\)。我們掃瞄到\(2\)的時候讓\(b[2]+=3\),代表從\(2\)開始能夠延伸到\(2\)的路徑有\(3\)條。然而掃瞄到\(4\)的時候事實上只有\(2\)條路徑滿足了,因此在統計完\(3\)以後應當\(--b[2]\),因為\((2,3)\)這條路徑不可能延伸到\(4\)了。但是如果還存在一條路徑\((1,3)\)呢?乙個終點可能對應多個起點,所以我們需要用乙個\(vector\)來維護每個終點對應的所有起點,在掃瞄到這個終點的時候掃一遍\(vector\)全部減掉

事實上,這就是差分。只不過普通差分只需要統計次數,而不會規定起點的深度。對於這個規定起點深度的問題,自然需要排除深度不正確的起點。所以才會需要用\(vector\)來記錄

我們發現,當\(s=1\)時,所有路徑都是從根節點往下。這樣的路徑有什麼特點?對於任意乙個點\(u\),由於它的路徑一定是從根節點出發的,因此根節點到它的路徑長度就是它的深度(根節點深度為0)。也就是說,這要滿足\(dep[u]=w[u]\),這個點就是能夠被觀察到的

那麼到底會被觀察到幾次呢?這就取決於根節點到它有幾條路徑。或者用更便於統計的方法:\(u\)及它的子樹內有多少個終點

和前者反了一下,但也更巧妙。所有路徑都是從下往上到根節點的

我們發現一條路徑上的乙個點如果要被觀察到,首先應該滿足$$dep[s]-dep[u]=w[u]$$移項我們發現,也就是$$dep[u]+w[u]=dep[s]$$也就是說我們需要找的答案等價於,在節點\(u\)及其子樹中,有多少起點的深度為\(dep[u]+w[u]\)。

聯絡鏈狀那一部分的做法,其實就是把一維的拓展為了樹。由於所有路徑都是向上的,也就等價於鏈狀中所有路徑都是從右向左的。我們記錄乙個桶\(b[x]\)表示深度為\(x\)的起點有多少個。因此我們還是一樣,每遇到乙個起點就++。特別的幸運的是,我們還不需要開乙個\(vector\),遇到終點就減去對應起點,因為終點一定是根節點。每一次統計的答案也就是\(b[dep[u]+w[u]]\)

但是有乙個問題,我們這樣的做法的正確性必須保證是在\(u\)的子樹內。然而深度為\(x\)的起點有可能是之前在別的子樹內統計的。怎麼辦?其實我們是從下往上走,也就是要在處理完\(u\)的全部子樹以後才來處理\(u\),於是我們其實只需要考慮新增加的部分就可以了。也就是在\(dfs\)下去之前先記錄乙個\(b[dep[u]+w[u]]\)存為\(tmp\),在\(dfs\)結束以後的答案也就是\(b[dep[u]+w[u]]-tmp\)

附上如上子任務的** code

對於一般性的資料,所有路徑一定是從下往上,經過\(lca\),再從上往下。而資料提示了我們\(s=1\)和\(t=1\)。這就是在暗示我們將路徑分開來考慮!

因此,就像鏈的情況,正解就是需要判斷起點深度的樹上差分!

首先考慮所有的\((s,lca)\)的路徑。這些路徑的特性與\(t=1\)的路徑是一樣的。都需要滿足\(dep[u]+w[u]=dep[s]\),唯一的不同就在於需要像鏈狀一樣,維護乙個\(vector\)記錄終點對應的起點,在過終點時減去即可。注意,這裡的終點不是指\(t\),而是\(lca\)

最最難理解的就是\((lca,t)\)這一部分了。我們依舊分析路勁的特徵。發現被觀察到的點一定滿足$$w[u]+dep[t]-dep[u]=dis(s,t)$$移項得到$$dep[u]-w[u]=dep[t]-dis(s,t)$$。但是不像之前的\(dep[s]\),這個\(dep[t]-dis(s,t)\)是在樹上是沒有實際意義的(一定要說有的話,那就是將向上的半截路徑翻到上面去的深度)。

於是沒有辦法,我們需要模擬\((s,lca)\)部分的做法來處理這個部分。我們依然是從下往上做,與之前相反,每遇到乙個\(t\)就代表著進入了一條新的路徑,每遇到乙個\(lca\)就意味著離開了一條路徑。並且我們每遇到乙個終點,標記的不是對應起點,而是對應的\(dep[t]-dis(s,t)\);每遇到乙個\(lca\),減去的也是該路徑對應的\(dep[t]-dis(s,t)\)。因此我們此時需要兩個\(vector\)來存了

綜上就是演算法的基本思想。這裡還有兩個細節需要分析:

倍增的陣列開太小了除錯了近乙個小時\(qwq\)。

/*by dennyqi 2018*/

#include #include #include #include #define r read()

using namespace std;

typedef long long ll;

const int maxn = 300010;

const int maxm = 600010;

const int inf = 1061109567;

inline int max(const int a, const int b)

inline int min(const int a, const int b)

inline int read()

int n,m,x,y,len;

int first[maxm],nxt[maxm],to[maxm],cnt;

int f[maxn][25],dep[maxn],w[maxn],s[maxn],t[maxn],lca[maxn],nums[maxn],b[maxn],bk[maxn*2],ans[maxn];

vector s[maxn];

vector t1[maxn], t2[maxn];

inline void add(int u, int v)

void bz_init(int u, int _f, int d)

for(int i = first[u]; i; i = nxt[i])

}inline int lca(int a, int b)

if(a == b) return a;

for(int i = 20; i >= 0; --i)

return f[a][0];

}void dfs1(int u, int _f)

b[dep[u]] += nums[u];

ans[u] += b[dep[u] + w[u]] - tmp;

for(int i = 0, sz = s[u].size(); i < sz; ++i)

}void dfs2(int u, int _f)

for(int i = 0, sz = t1[u].size(); i < sz; ++i)

ans[u] += bk[dep[u] - w[u] + maxn] - tmp;

for(int i = 0, sz = t2[u].size(); i < sz; ++i)

}int main()

bz_init(1, 0, 0);

for(int i = 1; i <= n; ++i)

for(int i = 1; i <= m; ++i)

dfs1(1, 0);

dfs2(1, 0);

for(int i = 1; i <= m; ++i)

} for(int i = 1; i <= n; ++i)

return 0;

}

NOIP2016 天天愛跑步

時間限制 2 s 記憶體限制 512 mb 題目描述 小c同學認為跑步非常有趣,於是決定製作一款叫做 天天愛跑步 的遊戲。天天愛跑步 是乙個養成類遊戲,需要玩家每天按時上線,完成打卡任務。這個遊戲的地圖可以看作一棵包含n個結點和n 1條邊的樹,每條邊連線兩個結點,且任意兩個結點存在一條路徑互相可達。...

NOIP2016天天愛跑步

小c同學認為跑步非常有趣,於是決定製作一款叫做 天天愛跑步 的遊戲。天天愛跑步 是乙個養成類遊戲,需要玩家每天按時上線,完成打卡任務。這個遊戲的地圖可以看作一一棵包含 nn n個結點和 n 1n 1n 1條邊的樹,每條邊連線兩個結點,且任意兩個結點存在一條路徑互相可達。樹上結點編號為從11 1到nn...

NOIP2016 天天愛跑步

看這道題不爽很久了,但一直沒有開它,原因是我不會 我太菜了 看了題解還是寫不來,因為我不會線段樹合併。然後今天學了dsu on tree這種神奇的科技,成功把它a了,效率吊打線段樹合併。於是寫篇題解紀念一下。洛谷p1600 天天愛跑步 不帶修改的樹上路徑資訊的維護,很容易想到樹上差分。我們考慮一條路...