能力提升綜合題單 Part 8.3.3 最近公共祖先

兩個點的最近公共祖先,即兩個點的所有公共祖先中,離根節點最遠的一個節點。
求解最近公共祖先,常用的方法是樹上倍增或者樹鏈剖分。

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;
}

樹鏈剖分下次一定!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章