將區間的莫隊演算法拓展到樹上,以此來解決一些樹上路徑的問題
考慮為什麼普通莫隊為什麼只是排乙個序就可以把暴力的時間複雜度除以\(\sqrt n\)?
其原因是儲存了之前的結果,也就是指對於重複的部分不需要多餘的空間,只需要調整詢問的順序就可以在整體上得到最大的優化。
樹上莫隊也是如此,但是我們要考慮如何將一顆樹轉換成序列的形式,並且所有的操作都需要變成對乙個連續區間的詢問
樹上的路徑在一般情況下可以分成二種,一種是乙個節點是另乙個節點的\(lca\),另一種情況就是這兩個節點的\(lca\)是第三個點
兩種情況都面臨乙個共同的困難,如何判斷乙個點是不是在路徑上?尤拉序就派上了用場,如果乙個點不在我們需要的路徑上,那麼我們對操作所轉換成為的連續區間中一定包含兩個這個點(先不考慮怎麼轉換的區間),但同時,我們所選擇的區間一定也要包含目標路徑,所有無用的節點也必須出現兩次
以下的\(st[u]\)表示尤拉序中第一次出現\(u\)的位置,\(ed[u]\)表示尤拉序中第二次(其實也是最後一次)出現\(u\)的位置
假設\(st[u]
如果是第一種情況,很明顯的,選擇\(st[u]\sim st[v]\),
如果是第二種情況,選擇\(ed[u]\sim st[v]\),想象一下我們尤拉序是怎麼構造出來的,因為\(lca\)一定在\(u\)的子樹外,所以要選擇\(ed[u]\),但同時的,考慮到不能包含到\(v\)節點子樹內的節點,所以要選擇\(st[u]\),但是同時我們會把\(lca\)忽略掉,不過我們也只會忽略掉\(lca\),考慮兩條路徑\(u\sim lca\)和\(lca\sim v\),對於\(u\sim lca\)這條路徑,因為我們是在往上走,所以對於所有的有用的節點,只會出現1次,如果是其他節點,一定會先進入再出來,所以會出現兩次,對於\(lca\sim v\)這條路徑也是一樣的分析的方法,但是因為我們所有的操作都在以\(lca\)為根的這棵子樹內,所以在尤拉序中\(lca\)的兩次出現位置不會被包含
因為我們實際上把乙個節點數為\(n\)的數變成了乙個長度為\(2n\)的序列,同樣的,塊的大小要從\(\sqrt n\)變成\(\sqrt \)
所以時間複雜度依然是\(o(n\sqrt n)\)
傳送門並沒有什麼特別的
#include#include#include#include#include#includeusing namespace std;
int divi;
struct pr
return dp[u][0];
}void add(int pos)
void del(int pos)
void calc(int pos)
int main()
for(int i=1,u,v;i>u>>v;
g[u].push_back(v);
g[v].push_back(u);
} dfs(1,0);
for(int i=1,u,v;i<=m;i++)
else
p[i].id=i;
} sort(p+1,p+m+1);
l=r=cal=1;
t[val[id[1]]]++;
used[val[id[1]]]=1;
for(int i=1;i<=m;i++)
while(p[i].rwhile(lwhile(p[i].lif(p[i].lca)
calc(p[i].lca);
ans[p[i].id]=cal;
if(p[i].lca)
calc(p[i].lca);
} for(int i=1;i<=m;i++)
cout
}
樹上莫隊演算法
繼續回來寫部落格 記錄點有意思的題目什麼的。貌似寫過這個的沒多少人 所以我也記錄一點。首先序列上的莫隊大家都應該很熟悉了 那麼樹上的莫隊要怎麼搞呢?先來看個題目 spoj cot2 求樹上兩點間路徑上有多少個不同的點權。序列上的莫隊是把詢問按照左端點分塊了 可是樹上沒有左端點,怎麼辦呢?我們把樹分塊...
樹上莫隊演算法
樹上莫隊,顧名思義就是把莫隊搬到樹上。我們從一道題目入手 sdoi2018 原題識別 spoj count on a tree ii 題目意思很明確 給定乙個 n 個節點的樹,每個節點表示乙個整數,問 u 到 v 的路徑上有多少個不同的整數。像這種不帶修改數顏色的題首先想到的肯定是樹套樹莫隊,那麼如...
樹上莫隊演算法
樹上莫隊,顧名思義就是把莫隊搬到樹上。我們從一道題目入手 sdoi2018 原題識別 spoj count on a tree ii 題目意思很明確 給定乙個 n 個節點的樹,每個節點表示乙個整數,問 u 到 v 的路徑上有多少個不同的整數。像這種不帶修改數顏色的題首先想到的肯定是樹套樹莫隊,那麼如...