題面
給出一個N節點,S爲根的樹,M次詢問某兩個節點間的LCA
N<=500000,M<=500000
分析
LCA的方法一般有倍增,tarjan,樹鏈剖分,這裏主要介紹樹鏈剖分
這個題用vector的樹鏈剖分會超時(常數原因)
vector的其他方法,開O2貌似能過
size[u]:以u爲根節點的子樹的節點數
dep[u]:u結點的深度,樹根爲0,往下依層遞增
樹鏈剖分是對樹上的邊按規則進行一些劃分:
重邊:節點與其重兒子(所有兒子中size最大的)所連邊
輕邊:節點與其非重兒子連邊
重鏈:重邊相連
輕鏈:輕邊
經過規則,發現重鏈的dep一定是單調的,即不會有這樣的重鏈:(粗邊是重鏈)
應當是這樣的:
引入一個top[u],如果u在重鏈上,則top[u]能跳到這條重鏈上dep最小的那個點,對於輕鏈,會跳到自己頭上。
這些構建完成後,樹鏈剖分滿足定理:從根到任意一個點,中間的重鏈數和輕鏈數均不會超過log n
在求LCA時,如果一個個節點跑,那麼面對M次查詢就會很差,甚至一次就需要N次計算。
但是樹鏈剖分中,可以在重鏈上跳,就大大縮短了計算的次數,意思就是如果當前點處於一個重鏈上,可以直接跳到重鏈的top,直到兩者的top相同(即處在同一條重鏈上/處於同一個輕邊的同一個端點)
用fa數組進行迭代,fa[top[u]]即可不論輕重邊一直跳。
中間過程都讓深度更大的一個優先往上跳(淺的優先跳會跳的過多導致非“最近”公共祖先)
終止條件即兩個點跳到了同一條重鏈上/一個輕點上,dep的單調性導致了兩個不同重鏈之間至少有一個輕鏈,也就不會跳得太多
具體的過程需要兩個dfs,第一次先標記出son,第二次連接起來這些son
代碼
結構體存邊版
#include "cstdlib"
#include "cstdio"
#include "iostream"
#include "vector"
using namespace std;
#define MAXN 500005
int dep[MAXN] , fa[MAXN] , son[ MAXN ] , size [ MAXN ] , top [ MAXN ];
#define cur v[i].t
struct edge
{
int t,nextt;
}v[MAXN<<1];
int last[MAXN];
void dfs1( int x )
{
size[x] = 1;
for(register int i = last[x] ; i ; i = v[i].nextt)
if(cur!=fa[x])
{
fa[cur]=x;dep[cur] = dep[x] + 1;
dfs1(cur);
size[x] += size[cur];
if( size[son[x]] < size[cur] )son[x] = cur;
}
}
void dfs2( int x , int t)
{
top[x] = t;
if(son[x])dfs2( son[x] , t );
for(register int i = last[x] ; i ; i = v[i].nextt)
if(cur!=fa[x]&&cur!=son[x])
dfs2(cur , cur);
}
inline int read()
{
register int x=0,ch=getchar();
while( !isdigit(ch))ch=getchar();
while(isdigit(ch))x = x * 10 + ch -'0' , ch=getchar();
return x;
}
int tot = 0;
inline void insert( int &from , int &to)
{
v[++tot].t = to;
v[tot].nextt = last[from];
last[from] = tot;
v[++tot].t = from;
v[tot].nextt = last[to];
last[to] = tot;
}
int main()
{
int n , m , s;
n=read();m=read();s=read();//結點個數、詢問的個數和樹根結點的序號
register int f , g;
for(register int i = 1 ; i < n ; i++)
{
f=read();
g=read();
insert( f , g );
}
dfs1(s);
dfs2(s ,s);
for(register int i = 0 ; i < m ; i++)
{
f=read();g=read();
while(top[f]!=top[g])
{
if(dep[top[f]]>dep[top[g]])f=fa[top[f]];
else g=fa[top[g]];
}
printf("%d\n",dep[f]<dep[g]?f:g);
}
return 0;
}
類封裝+vector存邊(不開O2 T3個點,開O2,T2個點)
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
class Tree_Chain {
private:
const static int MAXN = 500005;
int siz[MAXN];//表示其子樹的節點數
int fa[MAXN];//當前節點的父節點
int son[MAXN];//節點的重兒子
int top[MAXN];//重鏈頂
int dep[MAXN];//當前節點深
//int l[MAXN];//dfs序
std::vector<int>linker[MAXN];//存邊
public:
int dfs_clock = 0;
void dfs1(int x)//x是當前節點(當前樹根)
{
int cur;
siz[x] = 1;
for (int i = 0; i < linker[x].size(); i++)
{
cur = linker[x][i];
if (cur != fa[x]) //不是fa
{
dep[cur] = dep[x] + 1;//更新dep
fa[cur] = x;//更新fa
dfs1(cur);
siz[x] += siz[cur];//更新siz
if (siz[cur] > siz[son[x]])son[x] = cur;//更有可能成爲重兒子
}
}
}
void dfs2(int x, int t)//當前節點x,當前重鏈頂的編號
{
//l[x] = ++dfs_clock;//更新dfs序
//a[dfs_clock] = b[x];//
top[x] = t;
if (son[x])dfs2(son[x], t);//繼續連重鏈
int cur;
for (int i = 0; i < linker[x].size(); i++)
{
cur = linker[x][i];
if (cur != fa[x] && cur != son[x])//非父親非重兒子
dfs2(cur, cur);//在其他兒子上重鏈
}
}
void insert(int &u, int &v)
{
linker[u].push_back(v);
linker[v].push_back(u);
}
int Lca(int &u, int &v)
{
while (top[u]!=top[v])
{
if (dep[top[u]] > dep[top[v]])u = fa[top[u]];
else v = fa[top[v]];
}
return dep[u] < dep[v] ? u : v;
}
}TC;
int main()
{
ios::sync_with_stdio(false);
int n, m, s, u, v;
cin >> n >> m >> s;
for (register int i = 1; i < n; i++)
{
cin >> u >> v;
TC.insert(u, v);
}
TC.dfs1(s);
TC.dfs2(s, s);
for (register int i = 0; i < m; i++)
{
cin >> u >> v;
cout << TC.Lca(u, v)<<endl;
}
return 0;
}