兩個點的最近公共祖先,即兩個點的所有公共祖先中,離根節點最遠的一個節點。
求解最近公共祖先,常用的方法是樹上倍增或者樹鏈剖分。
1.P3379 【模板】最近公共祖先(LCA)
倍增法求lca,tarjan還沒學…
總體思路是記錄每個點的深度,並記錄它的第2^i的祖先的是哪個節點
把log2(i)+1進行打表儲存
求lca的時候先讓x>y,然後跳到同一深度
倍增跳到他們的lca的下一層,最後返回父節點就ok
用這個圖來舉例
17和18的lca,18的深度是7,17的深度是6
18先跳到16,跟17深度同爲6
16-8-5
17-10-7
就都達到了lca的下一層,返回fa[x][0]即可
#include<bits/stdc++.h>
using namespace std;
struct edge{
int to,nex;
}e[1000005];
int head[500005],cnt;
void add(int x,int y){
e[++cnt].to=y;
e[cnt].nex=head[x];
head[x]=cnt;
}
int depth[500005],fa[500001][22],lg[500005];
void dfs(int now,int fath){//now表示當前節點, fath表示父親節點
fa[now][0]=fath;
depth[now]=depth[fath]+1;
for(int i=1;i<=lg[depth[now]];i++){
fa[now][i]=fa[fa[now][i-1]][i-1];//意思是now的2^i祖先等於now的2^(i-1)祖先的2^(i-1)祖先
} //2^i = 2^(i-1) + 2^(i-1)
for(int i=head[now];i;i=e[i].nex){
if(e[i].to!=fath)dfs(e[i].to,now);
}
}
int lca(int x,int y){
if(depth[x]<depth[y])swap(x,y);//用數學語言來說就是:不妨設x的深度 >= y的深度
while(depth[x]>depth[y]){
x=fa[x][lg[depth[x]-depth[y]]-1];//先跳到同一深度
}
if(x==y)return x;//如果x是y的祖先,那他們的LCA肯定就是x了
for(int k=lg[depth[x]]-1;k>=0;k--){//不斷向上跳(lg就是之前說的常數優化)
if(fa[x][k]!=fa[y][k]){ //因爲我們要跳到它們LCA的下面一層,所以它們肯定不相等,如果不相等就跳過去。
x=fa[x][k];
y=fa[y][k];
}
}
return fa[x][0]; //返回父節點
}
int main(){
int n,m,s;
cin>>n>>m>>s;
for(int i=1;i<=n-1;i++){
int x,y;
scanf("%d %d",&x,&y);
add(x,y);
add(y,x);
}
for(int i=1;i<=n;i++){//預先算出log_2(i)+1的值,用的時候直接調用就可以了
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
}
dfs(s,0);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d %d",&x,&y);
printf("%d\n",lca(x,y));
}
return 0;
}
2.P3938 斐波那契
這題好巧妙…
但是如果熟悉斐波那契數列的性質話可以秒殺(我不會 嗚嗚嗚)
通過性質我們可以知道,每個兔子在出生的第二個月會生孩子
比如 1 1 2 3 5 8 13 21
2生了2個孩子,跟3相加變成了5
3生了3個孩子,跟5相加變成了8
觀察題目給出的圖
我們可以驚喜的發現6 7 8分別是1 2 3的孩子,他們中間剛好隔了一個斐波那契數5!
於是我們找到了規律,任意一個點跟它的父親節點相差 <=這個節點的第一個斐波那契數
比如11,他前面的這個數就是8,父親節點爲3
7,他前面這個數就是5,父親節點爲2
由於斐波那契數列單調,我們可以打表然後二分尋找這個數
這樣我們就可以不斷尋找u和v的祖先,直到相等,即爲這兩個點的lca
(記得long long)
#include<bits/stdc++.h>
using namespace std;
#define int long long
int fib[65],n,a,b;
signed main(){
fib[1]=1;
for(int i=2;i<=65;i++){
fib[i]=fib[i-1]+fib[i-2];
}
cin>>n;
for(int i=1;i<=n;i++){
scanf("%lld%lld",&a,&b);
while(a!=b){
if(a<b)swap(a,b);
int k=lower_bound(fib+1,fib+1+65,a)-fib;
a-=fib[k-1];
}
cout<<a<<endl;
}
return 0;
}
3.P4281 [AHOI2008]緊急集合 / 聚會
題意:尋找樹上的一個點,使a,b,c三個點到它的的距離最小
思路:lca lca lca,三對lca,然後一定會有兩對lca是同一個點,選擇lca相同的的兩對的lca做我們要選的點,並求出ans即可
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
long long ans=0;
int n,m,a,b,c,cnt;
int fa[maxn][25],lg[maxn],dep[maxn],t,head[maxn];
struct edge{
int v,nex;
}e[maxn];
inline void add(int u,int v){
e[++cnt].v=v;
e[cnt].nex=head[u];
head[u]=cnt;
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
while(dep[x]>dep[y]){
x=fa[x][lg[dep[x]-dep[y]]-1];
}
if(x==y)return x;
for(int k=lg[dep[x]];k>=0;k--){
if(fa[x][k]!=fa[y][k]){
x=fa[x][k];
y=fa[y][k];
}
}
return fa[x][0];
}
void dfs(int now,int fath){//now表示當前節點, fath表示父親節點
fa[now][0]=fath;
dep[now]=dep[fath]+1;
for(int i=1;i<=lg[dep[now]];i++){
fa[now][i]=fa[fa[now][i-1]][i-1];//意思是now的2^i祖先等於now的2^(i-1)祖先的2^(i-1)祖先
} //2^i = 2^(i-1) + 2^(i-1)
for(int i=head[now];i;i=e[i].nex){
if(e[i].v!=fath)dfs(e[i].v,now);
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n-1;i++){
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
}
for(int i=1;i<=n;i++){//預先算出log_2(i)+1的值,用的時候直接調用就可以了
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
}
dfs(1,0);
int t;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&a,&b,&c);
int t1=lca(a,b);
int t2=lca(a,c);
int t3=lca(b,c);
if(t1==t2){
t=t3;
}
else if(t1==t3){
t=t2;
}
else if(t2==t3){
t=t1;
}
ans=dep[a]+dep[b]+dep[c]-dep[t1]-dep[t2]-dep[t3];
printf("%d %lld\n",t,ans);
}
return 0;
}
樹鏈剖分下次一定!