洛谷 P3379 LCA (樹鏈剖分法)

題面

給出一個N節點,S爲根的樹,M次詢問某兩個節點間的LCA
N<=500000,M<=500000

分析

LCA的方法一般有倍增,tarjan,樹鏈剖分,這裏主要介紹樹鏈剖分
這個題用vector的樹鏈剖分會超時(常數原因)
vector的其他方法,開O2貌似能過

size[u]:以u爲根節點的子樹的節點數
dep[u]:u結點的深度,樹根爲0,往下依層遞增

樹鏈剖分是對樹上的邊按規則進行一些劃分:
重邊:節點與其重兒子(所有兒子中size最大的)所連邊
輕邊:節點與其非重兒子連邊
重鏈:重邊相連
輕鏈:輕邊

經過規則,發現重鏈的dep一定是單調的,即不會有這樣的重鏈:(粗邊是重鏈)
invalid
應當是這樣的:
valid

引入一個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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章