linki
kumokunn
Ranger
1
2
4
5
3
6
1
2
4
5
3
6
1
2
4
5
3
6
某個節點到根節點的最短距離
深度0
深度2
深度1
1
2
4
5
3
6
1
2
4
5
3
6
特殊的點
以他為根時
子樹大小\(<=\frac{n}{2}\)
可能有1~2個
2個時兩重心相鄰
1
2
4
5
3
6
重心
樹中最長的不重複路徑
1
2
4
5
3
6
只有父親沒有兒子
1
2
4
5
3
6
\(O(N)\)
1
2
4
5
3
6
起點
端點A
端點B
\(O(N)\)
void dfs(int pos,int pre){
sub[pos]=1;
wei[pos]=0;
for(auto i:dis[pos]){
if(i!=pre){
dfs(i,pos);
sub[pos]+=sub[i];
wei[pos]=max(wei[pos],sub[i]);
}
}
wei[pos]=max(wei[pos],n-sub[pos]);
if(wei[pos]<=n/2){
if(centroid[0]==0){
centroid[0]=pos;
}else centroid[1]=pos;
}
}
int DFS_cen(int u, int pa) {
for (int v : G[u]) {
if (vis[v] || v == pa) continue;
if (siz[v] * 2 > siz[u])return DFS_cen(v, u);
}
return u;
}
樹上最低共同祖先
1
2
4
5
3
6
LCA
1
2
4
5
3
6
LCA
1
2
4
5
3
6
LCA
倍增
樹壓平+RMQ(樹壓平講
初始化\(O(NlogN)\)查詢\(O(logN)\)
跳到同深度後
你發現再往上跳時有單調性
但你又不能用一般的二分搜
於是你用倍增
1
2
4
5
3
6
函數:往上跳x步後
同一個點回傳1
不同回傳0
步數 | 0 | 1 | 2 | 3 |
回傳值 | 0 | 1 | 1 | 1 |
從最大步的開始試
若跳完點不一樣就跳
反之則不跳
全部跳完後再往上跳一步
將答案視為跳\(x\)次後是LCA
有\(\Sigma^{n-1}_{k=0}2^k<2^n\)的性質
可知道當跳了還不是相同點時一定要跳
不然之後不可能會到LCA
反之可能超過LCA
int lca(int a,int b){
if(dep[a]>dep[b])swap(a,b);
int jump=dep[b]-dep[a];
for(int i=0;i<=ma;i++){
if(jump&(1<<i))b=rmq[b][i];
}
if(a==b)return a;
for(int i=ma;i>=0;i--){
if(rmq[a][i]!=rmq[b][i]){
a=rmq[a][i];b=rmq[b][i];
}
}
return rmq[a][0];
}
*記得把跟節點的祖先設為自己
扣的
void dfs(int pos,int pre){
arr[cnt++]=pos;
for(auto i:gra[pos]){
if(pos!=pre){
dfs(i);
}
}
arr[cnt++]=pos;
}
\(O(N)\)
1
2
4
5
3
6
5
4
2
1
12
10
9
11
8
7
6
5
4
3
2
1
6
6
3
2
1
4
5
3
另一種扣的
void dfs(int pos,int pre){
arr[cnt++]=pos;
for(auto i:gra[pos]){
if(pos!=pre){
dfs(i,pos);
arr[cnt++]=pos;
}
}
}
1
2
4
5
3
6
5
4
2
1
8
9
7
6
4
3
2
1
6
3
2
1
2
3
1
10
11
5
對一個點\(u\)來說
他的子樹中的點\(v\)會有
\(Tin_u<Tin_v,Tout_u>Tin_v\)
可以以此性質搭配區間操作
來對子樹做事
void dfs(int pos,int pre){
arr[cnt++]=pos;
for(auto i:gra[pos]){
if(pos!=pre){
dfs(i);
arr[cnt++]=pos;
}
}
}
兩個點的LCA是他們在序列中位置間
深度最小的
1
2
3
原因樹鏈剖分一起講
節點到根節點的所有節點和
扣的
void dfs(int pos,int pre){
for(auto i:gra[pos]){
if(i!=pre){
dp[i]+=dp[pos];
dfs(i,pos);
}
}
}
\(O(N)\)
又稱換根DP
額外記錄一些東西
在DFS時想辦法讓節點DP值可由其他節點推導
全方位木DP需要你可以快速做到這件事
a.k.a.輕重鏈剖分、HLD
把節點與子樹最大的兒子連起來
1
2
4
5
3
6
重鍊
輕鍊
輕鍊
重鍊
建立\(O(N)\) 查詢\(O(logN)\)
1
2
4
5
3
6
5
4
2
1
6
5
4
3
2
1
6
3
1
2
4
5
3
6
5
4
2
1
6
5
4
3
2
1
6
3
LCA
要記每條鍊深度最低的節點
每個點的父親
每個點在序列上的位置
跳鍊時跳鍊的最上面較深的點(?
void dfs_siz(int pos,int pre){
siz[pos]=1;
for(auto i:edg[pos]){
if(i!=pre){
dep[i]=dep[pos]+1;
dfs_siz(i,pos);
siz[pos]+=siz[i];pr[i]=pos;
}
}
}
void hld(int pos,int pre,int tp){
in[pos]=cnt++;top[pos]=tp;
int hchi=-1,hsiz=-1;
for(auto i:edg[pos]){
if(i!=pre)if(hsiz<siz[i]){
hsiz=siz[i];hchi=i;
}
}
if(hchi==-1)return;
hld(hchi,pos,tp);
for(auto i:edg[pos]){
if(i!=pre&&i!=hchi)hld(i,pos,i);
}
return;
}
int path(int a,int b){
int ans=0;
while(top[a]!=top[b]){
if(dep[top[a]]>dep[top[b]])swap(a,b);
ans=max(ans,query(1,1,n,in[top[b]],in[b]));
b=pr[top[b]];
}
if(dep[a]>dep[b])swap(a,b);
ans=max(ans,query(1,1,n,in[a],in[b]));
return ans;
}
基本型態:
利用重心把樹分成數個子樹
在遞迴求解,此時因重心特性
最差時
\(T(N)=2T(\frac{N}{2})+f(N)\)
\(f(n) \in O(n)\)時\(T(N) \in O(NlogN)\)
常要維護一些東西和在遞迴過程中額外計算
void centroid_decomp(int node = 1) {
int centroid = get_centroid();
processed[centroid] = true;
for (int i : graph[centroid]) if (!processed[i]) centroid_decomp(i);
}
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1E5+10;
vector<int> G[MAXN];
int sz[MAXN], centree[MAXN];
//centree[x] = 重心樹上的父節點
bool visited[MAXN];
void getsz(int x, int f){
sz[x]=1;
for(int i:G[x]){
if(visited[i]||i==f) continue;
getsz(i,x);
sz[x]+=sz[i];
}
}
int findcentroid(int x, int f, int s){
for(int i:G[x]){
if(2*sz[i]>=s&&i!=f&&!visited[i]){
return findcentroid(i,x,s);
}
}
return x;
}
void getcentree(int x, int f){
getsz(x,f);
int cent=findcentroid(x,f,sz[x]);
centree[x]=f;
visited[cent]=1;
for(int i:G[cent]){
if(!visited[i]) getcentree(i,cent);
}
}