【全程NOIP計劃】樹上問題
【全程NOIP計劃】樹上問題
最近公共祖先
問題
給定一棵樹,每次給兩個點,求他們的祖先,且該祖先為深度最小
思路
一般來說,我們想到一個暴力做法
查詢x,y的話,直接把x的祖先全部標記一遍,然後把y向上遍歷,直到遍歷到一個點,使得這個點被標記過,這個點就是x和y的最近公共祖先
或者,使得深度更大的第一個點一直向上跳,讓x和y的深度一樣,讓他們一起一步一步向上跳,直到他們遇到同一個點,這樣的方法正確性沒有問題,它的複雜度和這棵樹的深度是有關的
考慮倍增,對於剛才的做法進行優化
原來的方法:\(fa[i]\)表示i的父親
倍增的方法:\(fa[i][j]\)表示i向上跳\(2^j\)到哪個點
怎麼預處理呢?
f[i][0];
for(int j=1;j<=logn;j++)
{
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
}
這樣我們就維護出來了
所以倍增lca的步驟就是:
1.先讓y的深度比x大,然後將y和x調到深度相同的位置
2.然後把log從小到大列舉,如果跳完了到的是同一個點,他們就不跳,如果不是同一個點,那麼他們就向上跳,這樣重複的話一定可以跳到同一個點
3.我們可以唯一確定該這個拆分(不考慮順序
基礎問題
題目
給一個n個點的樹,有m次查詢,每次查x到y路上的點權和
思路
可以利用樹上字首和和最近公共祖先
發現答案就是x的深度加上y的深度,減去lca的深度,再減去lca父親的深度就可以了
又一個基礎問題
題目
給一個n個節點的樹,有m次查詢,每次給定兩個點x,y,求x是不是y的祖先
思路
直接判斷x和y的最近公共祖先是不是他們其中一個不就得了
不不不,下面有一個神奇的玩意兒
DFS序
實際上就是維護一下一個點是什麼時候dfs到的,或者什麼時候走入它的子樹,走出它的子樹的
int cnt;
void dfs(int x)
{
l[x]=++cnt;
for(x的每個兒子)
dfs(對應的兒子);
r[x]=cnt;
}
對於x子樹內的每個y,都有\(l[x]\le l[y]\le r[x]\)
上面的那道題目直接看y的區間是不是在進入x的和走出x的區間之內就好了
P3128
思路
x到根的路徑加一,然後\(b[x]++\)
P2680 運輸計劃
思路
最長的最小,讓我們想到了二分答案
發現,兩條路徑只能變短,不能變長
我們可以用一個樹上差分做法來做
經典問題
題目
給一個n個節點的數,有點權,有m次查詢,每次給三個數x,y,z,求x,y之間有多少點值=z,假設值域\(O(n)\)
思路
樹上差分的做法
直接查一個點到根上面的z有多少個,dfs到一個點的時候,我們直接維護一個值域上的陣列
未公開題目
題目
給一棵n個節點的樹,有m次詢問
每次查詢:從x點開始走向y點,每秒走一步,假設在第i秒走到了i點,則答案加一,求答案
思路
P1600 天天愛跑步
思路
每個選手可以被差分掉
把x到y的路徑的選手先分成x到lca,以及lca到y
LOJ6276
思路
P1967 貨車運輸
思路
P1352 沒有上司的舞會
思路
就一個樹形DP,騷簡單
未公開題目
題目
一個圖,保證每個點最多在一個簡單環裡面,問你這個圖上面有多少個眼鏡