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

    xiaoxiao2025-06-24  5

    题目链接: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; }
    最新回复(0)