LCA_ST算法

#include<bits/stdc++.h>
using namespace std;
const int maxn = 4e4+5;
struct Edge{
	int v,next,val;
}edge[maxn*2]; //order 和 dep 需要开点的两倍 因为dfs序的时候记录两次 
int fir[maxn],order[2*maxn],dep[2*maxn],head[maxn],dis[maxn],father[maxn][25];//fir表示节点第一次出现的dfs标号 order记录它的dfs序的标号包括了回溯时经过的 
int cnt,tot,dp[2*maxn][50],log_2[2*maxn],depth[maxn];//真正的记录每个点的深度 与dep不同 
void init(){//找lca方法 如果找a 和 b 点 先通过first数组得到两点第一次出现的dfs序号,然后在这个区间l r中找到depth[]数组记录的区间段中深度最小的点就是他们的父节点的深度
//然后根据depth[]数组这个时候的下标 再去order[]即dfs序遍历记录的数组中对应即可找到LCA 
	cnt = 0,tot=0;
	memset(dp,0,sizeof(dp));
	for(int i=0;i<maxn;i++){
		fir[i],order[i]=0,dep[i]=0,head[i]=-1,dis[i]=0,log_2[i]=0;
	}	
}
void addedge(int from,int to,int val){
	edge[cnt].v = to;
	edge[cnt].next = head[from];
	edge[cnt].val = val;
	head[from] = cnt++; 
	
}
void dfs(int x,int pre,int dep_,int val){
	father[x][0]=pre;
	dis[x] +=val;
	order[++tot] = x;
	dep[tot] = dep_;
	depth[x] = depth[pre]+1;
	fir[x] = tot;//oder记录第tot次的时候dfs经过的点 同时这次点的深度 fir记录该点第一次出现的tot序号 
	for(int i=head[x];i!=-1;i=edge[i].next){
		int to = edge[i].v;
		if(to==pre)continue;//又回到它的父節點了 所以跳過繼續
		dfs(to,x,dep_+1,val+edge[i].val);
		order[++tot] = x;
		dep[tot] = dep_;
	}
}
void ST(){
	log_2[1]=0,dp[1][0]=1;
	for(int i=2;i<=tot;i++){
		dp[i][0] = i;//dp[i][j]记录RMQ的下标 
		log_2[i]=log_2[i-1];
        if((1<<log_2[i]+1)==i)
            ++log_2[i];
	}
	for(int j=1;(1<<j)<=tot;j++){
		for(int i=1;i+(1<<j)-1<=tot;i++){//区间是i,i+2^j-1 故长度是2^j 
			int mid = i + (1<<(j-1));
			if(dep[dp[i][j-1]] < dep[dp[mid][j-1]]) dp[i][j] = dp[i][j-1];//选择深度小的 
			else dp[i][j] = dp[mid][j-1];
		}
	} 
}
int RMQ(int x,int y){
	int l = fir[x],r=fir[y];//第一次出现的时候dfs的下标
	if(l>r)swap(l,r);
//	while((1<<(k+1))<=r-l+1)k++;  
	int k = log_2[r-l+1];
//	if(dep[dp[l][k]] < dep[dp[r-(1<<k)+1][k]])return dp[l][k]; 
//	return dp[r-(1<<k)+1][k];
	return dep[dp[l][k]] < dep[dp[r-(1<<k)+1][k]] ? dp[l][k]:dp[r-(1<<k)+1][k];
} 
int find(int x, int k){  
    int now = x, tmp = 0;  
    while (k){  
        if (k & 1)  
            now = father[now][tmp];  
        k >>= 1;  
        tmp++;  
    }  
    return now;  
}  //从第x个点向上找k个类似快速幂 
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		int n,m;
		scanf("%d%d",&n,&m);	
		init();
		for(int i=1;i<n;i++){
			int x,y,val;
			scanf("%d%d%d",&x,&y,&val);
			addedge(x,y,val);
			addedge(y,x,val);
		}
		int root = 1;
		dfs(root,1,1,0);
		for (int i = 1; i < 25; i++)  
        for (int j = 1; j <= n; j++)  
            father[j][i] = father[father[j][i - 1]][i - 1];  
		ST();
		for(int i=1;i<=m;i++){
			int x,y;
			scanf("%d%d",&x,&y);
			int ans = RMQ(x,y);//查询的点 
			int res = dis[x] + dis[y] - 2*dis[order[ans]]; 
//			cout<<ans<<endl; 
			printf("%d %d\n",res,order[ans]);
		} 
	}
}
/*
2
3 2
1 2 10
3 1 15
1 2
2 3

2 2
1 2 100
1 2
2 1
*/ 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值