最近共同祖先(lca)

1.朴素法

d数组记录的是u,v节点到根节点的深度

int lca(int u,int v){
	if(d[u]<d[v]){     //保证u所在的节点比较深
		swap(u,v); 
	}
	while(d[u]!=d[v]){  //让u和v处于同一深度
		u=father[u];
	}
	while(u!=v){       //将u和v同时上调
		u=father[u];
		v=father[v];
	}
	return u;
}

2.离线Tarjan算法 链接

一次性将要计算最近共同祖先的u,v读入,然后dfs的时候处理这些u和v

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int pre[maxn];
const int inf=1e9+7;
struct edge{
	int from,to,val;
};
bool compare(edge a,edge b){
	return a.val>b.val;
}
int find(int x){
	if(x!=pre[x]){
		pre[x]=find(pre[x]);
	}
	return pre[x];
}
void unite(int x,int y){
	pre[find(x)]=find(y);
}
map<pair<int,int>,int>mp;  //保存的边之间的权值
map<pair<int,int>,int>ans; //询问的答案
vector<int>vec[maxn];
vector<int>q[maxn];
vector<edge>shu;
pair<int,int>p[maxn];
int flag[maxn];
edge a[maxn];
int n,m,query;
int dis[maxn];
void dfs(int u,int fa){
	int minn=1e9+7;
	for(int i=0;i<vec[u].size();i++){
		if(vec[u][i]!=fa){
			minn=min(minn,mp[{u,vec[u][i]}]);
			dfs(vec[u][i],u);
		}
	}
	dis[u]=minn;
}
void tarjan(int u,int fa){
	for(int i=0;i<vec[u].size();i++){
		if(vec[u][i]!=fa){
			tarjan(vec[u][i],u);
			pre[find(vec[u][i])]=find(u);
		}
	}
	flag[u]=1;
	for(int i=0;i<q[u].size();i++){
		int now=q[u][i];
		if(flag[now]){
			cout<<u<<"和"<<now<<"的最近共同祖先是"<<find(now)<<"\n";
			ans[{u,now}]=dis[find(now)];
			ans[{now,u}]=dis[find(now)];
		}
	}
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		pre[i]=i;
	}
	for(int i=1;i<=m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		a[i].from=u,a[i].to=v,a[i].val=w;
	}
	sort(a+1,a+m,compare);
	int start=0;
	for(int i=1;i<=m;i++){
		int from=a[i].from,to=a[i].to,val=a[i].val;
		if(find(from)==find(to)){
			continue;
		}
		else{
			unite(from,to);
			shu.push_back(a[i]);
		}
	}
	for(int i=shu.size()-1;i>=0;i--){  //从最大生成树的最短边为树根开始构建树
		edge now=shu[i];
		int from=now.from,to=now.to,val=now.val;
		//cout<<"from "<<from<<" to "<<to<<" val "<<val<<"\n";
		if(!start) start=from;
		vec[from].push_back(to);
		vec[to].push_back(from);
		mp[{from,to}]=val;
		mp[{to,from}]=val;
	}
	int cnt=0;
	cin>>query;
	while(query--){
		int u,v;
		cin>>u>>v;
		p[++cnt].first=u,p[cnt].second=v;
		if(find(u)!=find(v)){
			ans[{u,v}]=ans[{v,u}]=-1;
			continue;
		}
		q[u].push_back(v);
		q[v].push_back(u);
	}
	memset(dis,-inf,sizeof(dis));
	for(int i=1;i<=n;i++){
		pre[i]=i;
	}
	dfs(start,-1);
	tarjan(start,-1);
	for(int i=1;i<=cnt;i++){
		cout<<p[i].first<<" "<<p[i].second<<"\n";
		cout<<ans[p[i]]<<"\n";
	}
}

3.倍增求lca  链接

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int pre[maxn][30],depth[maxn];
vector<int>vec[maxn];
int from[maxn],root=-1;
int n;
void dfs1(int u){
	for(int i=0;i<vec[u].size();i++){
		int now=vec[u][i];
		depth[now]=depth[u]+1;
		dfs1(now);
	}
}
void dfs2(int u){
	for(int j=1;(1<<j)<=n;j++){
		for(int i=1;i<=n;i++){
			pre[i][j]=pre[pre[i][j-1]][j-1];
		}
	}
}
int lca(int u,int v){
	if(depth[u]<depth[v]) swap(u,v);  //保证u的深度>=v的深度
	for(int i=20;i>=0;i--){
		if(depth[pre[u][i]]>=depth[v]){
			u=pre[u][i];
		}
		if(u==v){  //到达相同深度时两个节点相遇
			return u;
		}
	}
	for(int i=20;i>=0;i--){  //到达相同深度时两个节点没有相遇
		if(pre[u][i]!=pre[v][i]){
			u=pre[u][i];
			v=pre[v][i];
		}
	}
	return pre[u][0];
}
int main(){
	cin>>n;
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		vec[u].push_back(v);
		pre[v][0]=u;
		from[v]=1;
	}
	for(int i=1;i<=n;i++){
		if(from[i]==0){
			root=i;
		}
	}
	depth[root]=1;
	dfs1(root);
	dfs2(root);
	int q;
	cin>>q;
	while(q--){
		int u,v;
		cin>>u>>v;
		cout<<u<<"和"<<v<<"的最近共同祖先是"<<lca(u,v)<<"\n";
	}
}
/*
9
1 2
1 3
1 4
2 5
2 6
3 7
6 8
7 9
5
1 3
5 6
8 9
8 4
5 8
 */

4.树上差分

1.边差分

 times[i]作为差分数组,然后dfs一遍从下面往上记录子树的答案,求从子树到根的后缀和,最后算到times[u]的时候,times[u]=times[u]+所有子树的times[v],此时times[u]代表u->fa[u]这条边的权值大小

边差分模板

#include<bits/stdc++.h>
using namespace std;
int n;
const int maxn=2e5+5;
int depth[maxn],pre[maxn][40];
vector<int>vec[maxn];
int times[maxn];  //times[i]代表以i为儿子的边出现的次数
vector<pair<int,int>>ans;
map<pair<int,int>,int>mp;
void dfs1(int u,int fa){
	depth[u]=depth[fa]+1;
	pre[u][0]=fa;
	for(int i=0;i<vec[u].size();i++){
		int now=vec[u][i];
		if(now!=fa){
			dfs1(now,u);
		}
	}
}
void dfs2(){
	for(int j=1;j<=30;j++){
		for(int i=1;i<=n;i++){
			pre[i][j]=pre[pre[i][j-1]][j-1];
		}
	}
}
void dfs3(int u,int fa){  //统计出现次数的时候是把儿子的值往上面推
	for(int i=0;i<vec[u].size();i++){
		int now=vec[u][i];
		if(now==fa){
			continue;
		}
		else{
			dfs3(now,u);
			times[u]+=times[now];
		}
	}
	mp[{fa,u}]=times[u];
	mp[{u,fa}]=times[u];
}
int lca(int u,int v){
	if(depth[u]<depth[v]){
		swap(u,v);
	}
	for(int i=30;i>=0;i--){
		if(depth[pre[u][i]]>=depth[v]){
			u=pre[u][i];
			if(u==v){
				return v;
			}
		}
	}
	for(int i=30;i>=0;i--){
		if(pre[u][i]!=pre[v][i]){
			u=pre[u][i];
			v=pre[v][i];
		}
	}
	return pre[u][0];
}
int main(){
	cin>>n;
	for(int i=1;i<=n-1;i++){
		int u,v;
		cin>>u>>v;
		ans.push_back(make_pair(u,v));
		vec[u].push_back(v);
		vec[v].push_back(u);
	}
	dfs1(1,0);
	dfs2();
	int m;
	cin>>m;
	while(m--){
		int u,v;
		cin>>u>>v;
		int zuxian=lca(u,v);
		times[u]+=1;
		times[v]+=1;
		times[zuxian]-=2;
	}
	dfs3(1,0);
	//cout<<ans.size()<<"\n";
	for(auto it:ans){
		cout<<mp[it]<<" ";
	}

}

2.点差分

与边差分不同的是 算到last[u]的时候last[u]+所有儿子last[v]的值now代表u这个节点的值的大小,而边差分的now代表的是u到它的父亲那条边的值的大小

点差分模板

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
vector<int>vec[maxn];
int depth[maxn];
int pre[maxn][31];
int last[maxn];
int maxx=0;
int n,m;
void dfs1(int u,int fa){
	depth[u]=depth[fa]+1;
	pre[u][0]=fa;
	for(int v:vec[u]){
		if(v!=fa){
			dfs1(v,u);
		}
	}
}
void dfs2(int u,int fa){
	for(int v:vec[u]){
		if(v!=fa){
			dfs2(v,u);
			last[u]+=last[v];
		}		
	}
}
void init(){
	for(int j=1;j<=30;j++){
		for(int i=1;i<=n;i++){
			pre[i][j]=pre[pre[i][j-1]][j-1];
		}
	}
}
int lca(int u,int v){
	if(depth[u]<depth[v]) swap(u,v);
	for(int i=30;i>=0;i--){
		if(depth[pre[u][i]]>=depth[v]){
			u=pre[u][i];
		}
		if(u==v){
			return u;
		}
	}
	for(int i=30;i>=0;i--){
		if(pre[u][i]!=pre[v][i]){
			u=pre[u][i];
			v=pre[v][i];
		}
	}
	return pre[u][0];
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n-1;i++){
		int u,v;
		cin>>u>>v;
		vec[u].push_back(v);
		vec[v].push_back(u);
	}
	dfs1(1,0);
	init();
	while(m--){
		int u,v;
		cin>>u>>v;
		last[u]+=1;
		last[v]+=1;
		int fa=lca(u,v);
		//cout<<"lca "<<fa<<"\n";
		last[fa]-=1;
		last[pre[fa][0]]-=1;
	}
	dfs2(1,0);
	//cout<<last[4]<<"\n";
	for(int i=1;i<=n;i++){
		maxx=max(maxx,last[i]);
	}
	
	cout<<maxx<<"\n";
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值