补题记录-HDOJ 2586 How far away ?(树上倍增求最近公共祖先)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2586
题意:

How far away ?
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 28758 Accepted Submission(s): 11562

Problem Description
There are n houses in the village and some bidirectional roads connecting them. Every day peole always like to ask like this “How far is it if I want to go from house A to house B”? Usually it hard to answer. But luckily int this village the answer is always unique, since the roads are built in the way that there is a unique simple path(“simple” means you can’t visit a place twice) between every two houses. Yout task is to answer all these curious people.

Input
First line is a single integer T(T<=10), indicating the number of test cases.
For each test case,in the first line there are two numbers n(2<=n<=40000) and m (1<=m<=200),the number of houses and the number of queries. The following n-1 lines each consisting three numbers i,j,k, separated bu a single space, meaning that there is a road connecting house i and house j,with length k(0<k<=40000).The houses are labeled from 1 to n.
Next m lines each has distinct integers i and j, you areato answer the distance between house i and house j.

Output
For each test case,output m lines. Each line represents the answer of the query. Output a bland line after each test case.

Sample Input
2
3 2
1 2 10
3 1 15
1 2
2 3
2 2
1 2 100
1 2
2 1

Sample Output
10
25
100
100
题意就是给一个树,每个边都是无向的并且由正权值。有T个样例,每个样例有m次询问,找两点之间的路径长度。

思路

我刚开始看到这一题,思路就已经跑偏了,完全没想最近公共祖先,而是想的最短路径。由于数据范围比较大,并且还有查询,所以按照最短路跑一定会T,于是专牛角尖的我遍继续按照最短路的想法,想着如何优化算法,在错误的路上一去不复返。
其实想到最近公共组先的思路以后就很简单了。用倍增思想,先预处理数组f(i,k)和数组dis(i,k);其中f(i,k)表示节点i的第2的k次方辈祖先是哪个节点,dis(i,k)表示节点i距离它第2的k次方辈祖先的距离是多少。考虑到f(i,k)=f(f(i,k-1),k-1),dis(i,k)=dis(i,k-1)+dis(f(i,k-1),k-1),于是对于每一个节点x都可以递推得到所有的f(x,k),dis(x,k),所以可以用nlogn的时间,用bfs预处理出所有节点的f(i,k)和dis(i,k)的值。然后就是询问了。对于每一次询问想x,y,假设d[x]>d[y],那么用k=logn…2,1,0进行遍历,如果d[f(x,k)]>=d[y],就先更新lenx(从x到最近公共祖先的距离,leny同理)的值,lenx+=dis(x,k),然后更新x,x=f(x,k),当d[x]==d[y]时结束循环。这时就得到了拥有深度相同的x与y的值。此时对x和y同步向上更新,依然是用k=logn,…2,1,0进行循环,这时如果f(x,k)等于0(即不存在这个点),或者f(x,k)==f(y,k),都continue掉,如果f(x,k)==f(y,k),那么就break就行了,不然持续更新lenx,leny,x,y的值就行。最后,如果x!=y,那么就把x=f(x,0),y=f(y,0)(不要忘了更新lenx和leny),此时x,y就一定相等了。答案就是lenx+leny。
(其实dis只需要一维就够了,维护目前节点到根的距离,因为是树状结构,最后如果最近公共祖先是z,那么答案就是d[x]+d[y]-2*d[z]。但是为了强调倍增思想在树中的重要性,就都用倍增思想来维护了。)

代码


//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<iomanip>
#include<ostream>
#include<cstring>
#include<string.h>
#include<string>
#include<cstdio>
#include<cctype>
#include<vector>
#include<cmath>
#include<queue>
#include<set>
#include<stack>
#include<map>
#include<cstdlib>
#include<time.h>
#include<bitset>
#define pb push_back
#define _fileout freopen("out.txt","w",stdout)
#define _filein freopen("in.txt","r",stdin)
#define test(i) printf("ok%d\n",i)
using namespace std;
typedef double db;
typedef long long ll;
const double PI = acos(-1.0);
const ll MOD=998244353;
const ll MAXN=8e4+10;
const int INF=0x3f3f3f3f;
const ll ll_INF=9223372036854775807;
ll qm(ll a,ll b){ll ret = 1;while(b){if (b&1)ret=ret*a%MOD;a=a*a%MOD;b>>=1;}return ret;}
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
ll lcm(ll a,ll b){return (a*b)/gcd(a,b);}
int n,m;
int d[MAXN];
int f[MAXN][20];
int dis[MAXN][20];
int head[MAXN],nexts[MAXN];
int e[MAXN],w[MAXN];
int vis[MAXN];
int tot;
int number;
void add(int x,int y,int z)
{
	e[++tot]=y;
	w[tot]=z;
	nexts[tot]=head[x];
	head[x]=tot;
}
void bfs()
{
	d[1]=1;
	int x=1,y;
	queue<int>q;
	q.push(1);
	while(!q.empty())
	{
		x=q.front();
		q.pop();
		if(vis[x])continue;
		vis[x]=1;
		for(int i=head[x];i;i=nexts[i])
		{
			y=e[i];
			if(vis[y])continue;
			d[y]=d[x]+1;
			f[y][0]=x;
			dis[y][0]=w[i];
			int mid=2;
			for(int j=1;mid<=d[y];j++)
			{
				f[y][j]=f[f[y][j-1]][j-1];
				dis[y][j]=dis[y][j-1]+dis[f[y][j-1]][j-1];
				mid*=2;
			}
			q.push(y);
		}
	}
	return;
}
int slove(int a,int b)
{
	int x=a,y=b;
	int lenx=0,leny=0;
	for(int k=number;k>=0;k--)
	{
		if(d[x]==d[y])break;
		if(d[f[x][k]]<d[y])continue;
		if(d[f[x][k]]>=d[y]){lenx+=dis[x][k];x=f[x][k];}
	}
	if(x==y)
	{
		return lenx;
	}
	for(int k=number;k>=0;k--)
	{
		if(f[x][k]==f[y][k]||f[x][k]==0)continue;
		lenx+=dis[x][k];x=f[x][k];
		leny+=dis[y][k];y=f[y][k];
	}
	if(x!=y)
	{
		lenx+=dis[x][0];
		x=f[x][0];
		leny+=dis[y][0];
		y=f[y][0];
	}
	return lenx+leny;
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		memset(d,0,sizeof(d));
		memset(f,0,sizeof(f));
		memset(vis,0,sizeof(vis));
		memset(dis,0,sizeof(dis));
		memset(head,0,sizeof(head));
		memset(nexts,0,sizeof(nexts));
		tot=0;
		for(int i=1;i<n;i++)
		{
			int x,y,z;
			scanf("%d%d%d",&x,&y,&z);
			add(x,y,z);
			add(y,x,z);
		}
		number=log(n)/log(2);
		// if(int(pow(2,number)+0.5)<n)
			// number++;
		bfs();
		// for(int i=1;i<=n;i++)
		// {
		// 	for(int k=0;k<=number;k++)
		// 	{
		// 		printf("f[%d][%d]=%d dis[%d][%d]=%d\n",i,k,f[i][k],i,k,dis[i][k]);
		// 	}
		// }
		for(int i=1;i<=m;i++)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			if(d[x]<d[y])swap(x,y);
			int ans=slove(x,y);
			printf("%d\n",ans);
		}
	}
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值