題目型別: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 天天愛跑步 不帶修改的樹上路徑資訊的維護,很容易想到樹上差分。我們考慮一條路...