Lowest Common Ancestor

luoguP3379板子题

代码:
现写的一份,是单纯的LCA

#include<iostream>
using namespace std;
const int N = 500005;
int en = 0,head[N] = {0};
struct Edge{
	int to,next;
	int val;
}edge[N*2];
int n,m,q,a,b,c,rt;
int fa[N][21] = {0},depth[N] = {0},w[N] = {0};

void add(int u,int v){
	edge[++en].to = v;
	edge[en].next = head[u];
	head[u] = en;
}

void dfs(int u,int father){
	depth[u] = depth[father]+1;
	fa[u][0] = father;
	for(int i=1;(1<<i)<=depth[u];i++){
		fa[u][i] = fa[fa[u][i-1]][i-1];
	}
	for(int p=head[u]; p ;p=edge[p].next){
		int v = edge[p].to;
		if(v!=father) {
			w[v] = w[u]+edge[p].val ;
			dfs(v,u);
		}
	}
}

int lca(int u,int v){
	if(depth[u]<depth[v]) swap(u,v);
	for(int i=20;i>=0;i--){
		if(depth[fa[u][i]]>=depth[v])
			u = fa[u][i];
	}
	if(u==v) return u;
	for(int i=20;i>=0;i--){
		if(fa[u][i]!=fa[v][i]){
			u = fa[u][i];
			v = fa[v][i];
		}
	}
	return fa[u][0];
}

void query(int u,int v){
	int x = lca(u,v);
	int ans = w[u]+w[v]-2*w[x];
//	printf("%d\n",ans);
	printf("%d\n",x);
} 

int main(){
	scanf("%d%d%d",&n,&q,&rt);
	for(int i=1;i<n;i++){
		scanf("%d%d",&a,&b);
		add(a,b);
		add(b,a);
	}
	dfs(rt,0);
	for(int i=1;i<=q;i++){
		scanf("%d%d",&a,&b);
		query(a,b);
	}
	return 0;
} 

这份疑似是树链剖分………………无语住了就是说,我已经忘记什么是top了…… 是每条链的顶端结点

#include<bits/stdc++.h>
using namespace std;
const int N = 500005;
int en,n,m,s,head[N],depth[N],father[N],son[N],f[N][21],top[N],size[N];
struct Edge{
	int to,next;
}edge[N<<1];
inline void read(int &x){
	char ch;
	while((ch = getchar())<'0' || ch>'9') ;
	for(x=0;ch<='9'&&ch>='0';ch = getchar())
		x = x*10+ch-'0';
}

inline void add(int x,int y){
	edge[en].to = y;
	edge[en].next = head[x];
	head[x] = en++;
}

void init(){
	read(n);read(m);read(s);
	memset(head,-1,sizeof(head));
	for(int i=1,x,y;i<n;i++){
		read(x);read(y);
		add(x,y);
		add(y,x);
	}
}

void dfs1(int u,int f){
	depth[u] = depth[f]+1;
	size[u] = 1;
	father[u] = f;
	for(int p = head[u],v;~p;p = edge[p].next ){
		v = edge[p].to ;
		if(v==f) continue;
		dfs1(v,u);
		size[u] += size[v];
		if(size[v]>size[son[u]])
			son[u] = v;
	}
}

void dfs2(int u){
	if(son[u]) {
		top[son[u]] = top[u];
		dfs2(son[u]);
	}
	for(int v,p = head[u];~p;p = edge[p].next ){
		v = edge[p].to ;
		if(top[v]) continue;
		top[v] = v;
		dfs2(v);
	}
}

int lca(int x,int y){
	while (top[x]!=top[y]) {
		if(depth[top[x]]<depth[top[y]]) swap(x,y);
		x = father[top[x]];
	}
	return depth[x]<depth[y]?x:y;
}

void solve(){
	for(int i=0,x,y;i<m;i++){
		read(x);read(y);
		printf("%d\n",lca(x,y));
	}
}

int main(){
	init();
	dfs1(s,0);
	top[s] = s;
	dfs2(s);
	solve();
}

树上最短路

每条边有权值的,问两点间最短路

#include<iostream>
using namespace std;
const int N = 100005;
int en = 0,head[N] = {0};
struct Edge{
	int to,next;
	int val;
}edge[N*2];
int n,m,q,a,b,c;
int fa[N][21] = {0},depth[N] = {0},w[N] = {0};

void add(int u,int v,int _val){
	edge[++en].to = v;
	edge[en].next = head[u];
	head[u] = en;
	edge[en].val = _val;
}

void dfs(int u,int father){
	depth[u] = depth[father]+1;
	fa[u][0] = father;
	for(int i=1;(1<<i)<=depth[u];i++){
		fa[u][i] = fa[fa[u][i-1]][i-1];
	}
	for(int p=head[u]; p ;p=edge[p].next){
		int v = edge[p].to;
		if(v!=father) {
			w[v] = w[u]+edge[p].val ;
			dfs(v,u);
		}
	}
}

int lca(int u,int v){
	if(depth[u]<depth[v]) swap(u,v);
	for(int i=20;i>=0;i--){
		if(depth[fa[u][i]]>=depth[v])//要加等号…… 
			u = fa[u][i];
	}
	if(u==v) return u;
	for(int i=20;i>=0;i--){
		if(fa[u][i]!=fa[v][i]){
			u = fa[u][i];
			v = fa[v][i];
		}
	}
	return fa[u][0];
}

void query(int u,int v){
	int x = lca(u,v);
	int ans = w[u]+w[v]-2*w[x];
	printf("%d\n",ans);
} 

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
		add(b,a,c);
	}
	dfs(1,0);
	scanf("%d",&q);
	for(int i=1;i<=q;i++){
		scanf("%d%d",&a,&b);
		query(a,b);
	}
	return 0;
} 

tips:

  • 记得写lca的时候开fa[N][21],因为20的话会超出范围……
  • 记得写lca的时候,第一次要挪到depth相等的时候要取>=,否则根本等不了的,然后导致lca出错

三点最短路

在这里插入图片描述

#include<iostream>
using namespace std;
const int N = 50005;
int fa[N][18] = {0};
int depth[N] = {0},val[N] = {0},head[N] = {0};
int en,n,q,a,b,c;
struct Edge{
	int next,to;
	int value;
}edge[N*2];

void add(int u,int v,int w){
	edge[++en].to = v;
	edge[en].next = head[u];
	head[u] = en;
	edge[en].value = w;
}

void dfs(int u,int father){
	depth[u] = depth[father]+1;
	fa[u][0] = father;
	for(int i=1;(1<<i)<=depth[u];i++){
		fa[u][i] = fa[fa[u][i-1]][i-1];
	}
	for(int p=head[u]; p ;p=edge[p].next){
		int v = edge[p].to ;
		if(v!=father){
			val[v] = val[u]+edge[p].value ;
			dfs(v,u);
		}
	} 
}

int lca(int u,int v){
	if(depth[u]<depth[v]) swap(u,v);
	for(int i=17;i>=0;i--){
		if(depth[fa[u][i]]>=depth[v])
			u = fa[u][i];
	}
	if(u==v) return u;
	for(int i=17;i>=0;i--){
		if(fa[u][i]!=fa[v][i]){
			u = fa[u][i];
			v = fa[v][i];
		}
	}
	return fa[u][0];
}

int solve(int u,int v){
	if(depth[u]<depth[v]) swap(u,v);
	int x = lca(u,v);
	return val[u]+val[v]-2*val[x];
}

void query(int u,int v,int w){//uvw是节点啦 
	int x = lca(u,v);
	int y = lca(u,w);
	int z = lca(v,w);
	int ans = 0;
	if(depth[x]>=depth[y] && depth[x]>=depth[z]){
		ans = solve(u,v);
		ans += solve(w,lca(u,v));
	}
	else if(depth[y]>=depth[x] && depth[y]>=depth[z]){
		ans = solve(u,w);
		ans += solve(v,lca(u,w));
	}
	else if(depth[z]>=depth[x] && depth[z]>=depth[y]){
		ans = solve(v,w);
		ans += solve(u,lca(v,w));
	}
	printf("%d\n",ans);
	return ;
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<n;i++){
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
		add(b,a,c);
	}
	dfs(1,0);
	scanf("%d",&q);
	for(int i=1;i<=q;i++){
		scanf("%d%d%d",&a,&b,&c);
		query(a,b,c);
	}
	return 0;
} 

一开始的算法有缺陷……并不是那样做的……打了一些草稿后才明白是应该分这样几种情况
在这里插入图片描述

统一的做法是每次找到三个lca中最深的一个,然后爬上去,爬上去后处理另一个。

Marinia

在这里插入图片描述

#include<iostream>
using namespace std;
const int N = 100005;
int n,m,x,y,t,k,a,b;
int fa[N][21] = {0},depth[N] = {0};//果然……这里超出范围了,写21而不是20 
int tot;
int good[N][32] = {0}; 
bool need[32] = {0};
struct Edge{
	int to,next;
}edge[N*2];
int head[N] = {0};
int en = 0;

void add(int u,int v){
	edge[++en].to = v;
	edge[en].next = head[u];
	head[u] = en;
}

void dfs(int u,int father){
	fa[u][0] = father;
	depth[u] = depth[father] +1;
	for(int i=0;i<m;i++){
		good[u][i] += good[father][i];//从最顶上到自己(含自己)的货物量 
	}
	for(int i=1;(1<<i)<=depth[u];i++){
		fa[u][i] = fa[fa[u][i-1]][i-1];
	}
	for(int p=head[u];p;p=edge[p].next){
		int v = edge[p].to ;
		if(v!=father) dfs(v,u);
	}
}

bool query(int u,int v){
	int tmpu = u,tmpv = v;	
	for(int i=0;i<m;i++){
		u = tmpu;
		v = tmpv;
		if(need[i]==false) continue;
		bool f = false;
		if(depth[u]<depth[v]) swap(u,v);
		for(int j=20;j>=0;j--){
			if(depth[fa[u][j]]>=depth[v]){//可以上跳 
				if(good[u][i]-good[fa[u][j]][i]>0) f = true;
				u = fa[u][j];
			}
		}
		if(f==true) continue;
		if(u==v) {
			if(good[u][i]-good[fa[u][0]][i]>0) f = true;
			if(f) continue;
			if(!f) return false;
		}
		for(int j=20;j>=0;j--){
			if(fa[u][j]!=fa[v][j]){
				if(good[u][i]-good[fa[u][j]][i]>0) f = true;
				if(good[v][i]-good[fa[v][j]][i]>0) f = true;
				u = fa[u][j];
				v = fa[v][j];
			}
		}
		if(good[u][i]-good[fa[u][1]][i]>0) f = true;
		if(good[v][i]-good[fa[v][1]][i]>0) f = true;
		if(!f) return false;
	}
	return true;
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&tot);
		for(int j=1;j<=tot;j++){
			scanf("%d",&x);
			good[i][x]++;
		}
	}
	for(int i=1;i<n;i++){
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs(1,0);
	scanf("%d",&k);
	for(int i=1;i<=k;i++){
		scanf("%d%d%d",&a,&b,&t);
		for(int j=0;j<32;j++){
			need[j] = 0;
		}
		for(int j=1;j<=t;j++){
			scanf("%d",&x);//小心变量被你自己重复使用,还不如多开几个…… 
			need[x] = true;
		}
		if(query(a,b)) printf("YES\n");
		else printf("NO\n");
	}
	return 0;
} 

考前没有复习,导致考场上现场回忆链式前向星,写的是一个歪七扭八的版本,两个小时才拿了十分……
和前面的tips差不多:

  • 记得开fa[N][21],否则会超出范围,导致出错,具体出错的机理还不明确,只知道在找lca的途中会在第一层循环里面进行很多遍,应该是到20下标那里胡乱进行了一些赋值
  • 呃呃呃呃我很喜欢在输入变量多的时候进行一些变量的共用,可是很容易出错因为后面的可能会把前面的覆盖掉……fine,以后小心一点为好

树上路径交叉

在这里插入图片描述

#include<iostream>
using namespace std;
const int N = 100005;
int T,n,m,x,y,x1,y1,x2,y2;
int head[N] = {0},depth[N] = {0};
int fa[N][18] = {0};
struct Edge{
	int to,next;
}edge[N*2];
int en;

void add(int u,int v){
	edge[++en].to = v;
	edge[en].next = head[u];
	head[u] = en;
}

void dfs(int u,int father){
	depth[u] = depth[father]+1;
	fa[u][0] = father;
	for(int i=1;(1<<i)<=depth[u];i++){
		fa[u][i] = fa[fa[u][i-1]][i-1]; 
	}
	for(int p=head[u];p;p=edge[p].next){
		int v = edge[p].to ;
		if(v != father){
			dfs(v,u);
		}
	}
}

int lca(int u,int v){
	if(depth[u]<depth[v]) swap(u,v);
	for(int i=17;i>=0;i--){
		if(depth[fa[u][i]]>=depth[v]){
			u = fa[u][i];
		}
	}
	if(u==v) return u;
	for(int i=17;i>=0;i--){
		if(fa[u][i]!=fa[v][i]){
			u = fa[u][i];
			v = fa[v][i];
		} 
	}
	return fa[u][0];
}

bool check(int u1,int v1,int u2,int v2){
	int lca1 = lca(u1,v1);
	int lca2 = lca(u2,v2);
	if((lca(lca1,u2)==lca1 || lca(lca1,v2)==lca1) && depth[lca1]>=depth[lca2]) return true;
	if((lca(lca2,u1)==lca2 || lca(lca2,v1)==lca2) && depth[lca2]>=depth[lca1]) return true;
	return false;
}

int main(){
	scanf("%d",&T);
	while(T--){
		en = 0;
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++){
			head[i] = 0;
			for(int j=0;j<18;j++){
				fa[i][j] = 0;
			}
		}	
		for(int i=1;i<n;i++){
			scanf("%d%d",&x,&y);
			add(x,y);
			add(y,x);
		}
		dfs(1,0);
		for(int i=1;i<=m;i++){
			scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
			if(check(x1,y1,x2,y2))
				printf("YES\n");
			else printf("NO\n");
		}
	}
	return 0;
}

这里的思路是,若其中一条链的lca在另一条链上,则路径有交叉。而一点在链上有两种写法,第一种是:(lca(x,u)==x||lca(x,v)==x)&&depth[x]<=lca(u,v);第二种是:dis(u,v)==dis(x,u)+dis(x,v)
试试第二种哈:

int dis(int u,int v){
	return depth[u]+depth[v]-2*depth[lca(u,v)];
}

bool check(int u1,int v1,int u2,int v2){
	int lca1 = lca(u1,v1);
	int lca2 = lca(u2,v2);
	if(dis(lca1,u2)+dis(lca1,v2)==dis(u2,v2)) return true;
	if(dis(lca2,u1)+dis(lca2,v1)==dis(u1,v1)) return true;
	return false;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值