解题思路:LCA好题,和普通的LCA不同的是本题查询的是三个点。一开始我的做法很诡异,找三个点的关系,然后离线Tarjan求解。我共找到4个关系,但是总能找到反例拓展这些关系,写到后面if巨多代码就一坨了,代码一坨也就算了,还没AC,蛋疼菊紧!但是我很欣慰,做acm做这么久思维没有被算法死死束缚,我有自己的一些想法。
Wa到死之后开始想怎么把三个点的查询转换成两个点的常规查询,其实拆成三次查询,然后这三次查询的结果加起来除以2就是答案了。为什么呢?求三次查询,查询到的路径覆盖最终的路径两次,你可以在纸上画画看。有了这个转换之后就简单了,答案用个结构体储存,查询x,y,z,如果当前查询x,y,那么更新resx,如果查询y,z,那么更新resy,如果查询x,y,那么更新resz,最后相加除2.
用Tarjan离线算法ac了本题之后,想用LCA转RMQ解法来写下这题,就去学习学习。发现转换的方法其实挺简单的,我引用下这篇文章:http://blog.sina.com.cn/s/blog_5ceeb9ea0100kynz.html,里面讲得相当清楚。
对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、v的祖先且x的深度尽可能大。另一种理解方式是把T理解为一个无向无环图,而LCA(T,u,v)即u到v的最短路上深度最小的点。
这里给出一个LCA的例子:
例一
对于T=<V,E>
V={1,2,3,4,5}
E={(1,2),(1,3),(3,4),(3,5)}
则有:
LCA(T,5,2)=1
LCA(T,3,4)=3
LCA(T,4,5)=3
RMQ问题与LCA问题的关系紧密,可以相互转换,相应的求解算法也有异曲同工之妙。下面给出LCA问题向RMQ问题的转化方法。
对树进行深度优先遍历,每当“进入”或回溯到某个结点时,将这个结点的编号存入数组E最后一位。同时记录结点i在数组中第一次出现的位置(事实上就是进入结点i时记录的位置),记做R[i]。如果结点E[i]的深度记做D[i],易见,这时求LCA(T,u,v),就等价于求E[RMQ(D,R[u],R [v])],(R[u]<R[v]),其中RMQ(D,R[u],R [v])就是在D数组中求下标从R[u]到R[v]的最小值的下标。例如一,求解步骤如下:
数列E[i]为:1,2,1,3,4,3,5,3,1
R[i]为:1,2,4,5,7
D[i]为:0,1,0,1,2,1,2,1,0
于是有:
LCA(T,5,2) = E[RMQ(D,R[2],R[5])] = E[RMQ(D,2,7)] = E[3] = 1
LCA(T,3,4) = E[RMQ(D,R[3],R[4])] = E[RMQ(D,4,5)] = E[4] = 3
LCA(T,4,5) = E[RMQ(D,R[4],R[5])] = E[RMQ(D,5,7)] = E[6] = 3
易知,转化后得到的数列长度为树的结点数的两倍减一, 所以转化后的RMQ问题与LCA问题的规模同次。
再举一个例子帮助理解:
(1)
/ \(2) (7)
/ \ \
(3) (4) (8)
/ \
(5) (6)
一个nlogn 预处理,O(1)查询的算法.
Step 1:
按先序遍历整棵树,记下两个信息:结点访问顺序和结点深度.
如上图:
结点访问顺序是: 1 2 3 2 4 5 4 6 4 2 1 7 8 7 1 //共2n-1个值
结点对应深度是: 0 1 2 1 2 3 2 3 2 1 0 1 2 1 0
Step 2:
如果查询结点3与结点6的公共祖先,则考虑在访问顺序中
3第一次出现,到6第一次出现的子序列: 3 2 4 5 4 6.
这显然是由结点3到结点6的一条路径.
在这条路径中,深度最小的就是最近公共祖先(LCA). 即
结点2是3和6的LCA.
Step 3:
于是问题转化为, 给定一个数组R,及两个数字i,j,如何找出
数组R中从i位置到j位置的最小值..
如上例,就是R[]={0,1,2,1,2,3,2,3,2,1,0,1,2,1,0}.
i=2;j=7;
接下来就是经典的RMQ问题.
有了上面的基础,我们初始化好rmq,我们就可以在线o(1)的时间内输出答案,最终结果的处理方法和tarjan一样,三者求和除2.
测试数据:
4
0 1 1
1 2 2
1 3 3
1
1 2 3
4
0 1 1
0 2 2
0 3 3
1
1 2 3
6
0 1 1
0 2 2
1 3 3
1 4 4
5 2 5
1
4 5 3
4
0 1 1
1 2 2
2 3 3
2
0 1 2
1 2 3
4
0 1 1
0 2 2
1 3 3
2
0 1 2
1 2 3
7
0 1 1
1 2 2
1 3 3
2 4 4
4 5 5
3 6 6
3
1 2 3
0 2 3
0 4 6
7
0 1 1
0 2 2
1 3 3
1 4 4
2 5 5
2 6 6
5
0 1 3
1 0 2
3 0 6
4 1 5
4 2 5
C艹代码:
TarJan离线解法
#include <stdio.h> #include <string.h> #include <vector> using namespace std; #define MAX 150000 #define min(a,b) ((a)<(b)?(a):(b)) struct node { int y,z,len; }cur; struct Answer { int x,y,z; int resx,resy,resz; }ans[MAX]; int dist[MAX],fa[MAX]; int cnt,n,q,visit[MAX]; vector<node> tree[MAX]; vector<node> query[MAX]; void Initial() { memset(dist,0,sizeof(dist)); memset(ans,-1,sizeof(ans)); memset(visit,0,sizeof(visit)); for (int i = 0; i <= n; ++i) fa[i] = i,tree[i].clear(),query[i].clear(); } int Find(int x) { int r = x,tp; while (r != fa[r]) r = fa[r]; while (x != r) { tp = fa[x]; fa[x] = r,x = tp; } return r; } void LCA(int now,int dis) { fa[now] = now; dist[now] = dis; visit[now] = 1; int i,size = tree[now].size(); for (i = 0; i < size; ++i) { node cur = tree[now][i]; if (!visit[cur.y]) { LCA(cur.y,dis+cur.len); fa[cur.y] = now; } } size = query[now].size(); for (i = 0; i < size; ++i) { node cur = query[now][i]; if (visit[cur.y] == 0) continue; int in = cur.len; int pa = Find(cur.y); if (now == ans[in].x && cur.y == ans[in].y || now == ans[in].y && cur.y == ans[in].x) ans[in].resx = dist[now] + dist[cur.y] - 2 * dist[pa]; if (now == ans[in].y && cur.y == ans[in].z || now == ans[in].z && cur.y == ans[in].y) ans[in].resy = dist[now] + dist[cur.y] - 2 * dist[pa]; if (now == ans[in].x && cur.y == ans[in].z || now == ans[in].z && cur.y == ans[in].x) ans[in].resz = dist[now] + dist[cur.y] - 2 * dist[pa]; } } int main() { int i,j,k,flag = 0; int a,b,c,z; while (scanf("%d",&n) != EOF) { if (flag) printf("\n"); Initial(),flag = 1; for (i = 1; i < n; ++i) { scanf("%d%d%d",&a,&b,&c); cur.y = b,cur.len = c; tree[a].push_back(cur); cur.y = a,cur.len = c; tree[b].push_back(cur); } scanf("%d",&q); for (i = 1; i <= q; ++i) { scanf("%d%d%d",&a,&b,&c); cur.len = i; ans[i].x = a,ans[i].y = b,ans[i].z = c; cur.y = b,query[a].push_back(cur); cur.y = a,query[b].push_back(cur); cur.y = a,query[c].push_back(cur); cur.y = c,query[a].push_back(cur); cur.y = b,query[c].push_back(cur); cur.y = c,query[b].push_back(cur); } LCA(0,0); for (i = 1; i <= q; ++i) printf("%d\n",(ans[i].resx + ans[i].resy + ans[i].resz)/2); } }
#include <stdio.h> #include <string.h> #include <vector> using namespace std; #define MAX 151000 #define min(a,b) ((a)<(b)?(a):(b)) struct node { int v,len; }cur; vector<node> tree[MAX]; int dist[MAX],deep[MAX]; //dist记录每个结点到根节点的距离,deep记录深搜过程每次出现的结点对应的深度 int Index[MAX],first[MAX]; //index记录深搜过程每次出现的结点对应的编号,first记录每个结点在深搜过程中第一次出现的位置 int ans,cnt,n,q,visit[MAX]; //cnt记录深搜过程进入和回溯了多少次,最后cnt等于2 * n - 1 struct RMQ{ int dp[MAX][20]; void Create(); int Query(int l,int r); }rmq; void RMQ::Create() { int i,j = 2,k = 0; for (i = 1; i <= cnt; ++i) dp[i][0] = i; while (j <= cnt) j *= 2,k++; for (j = 1; j <= k; ++j) for (i = 1; i + (1<<j) - 1 <= cnt; ++i) { int index1 = dp[i][j-1]; int index2 = dp[i+(1<<(j-1))][j-1]; if (deep[index1] < deep[index2]) dp[i][j] = index1; else dp[i][j] = index2; } } int RMQ::Query(int l, int r) { int i = 2,j,k = 0; while (i <= r - l + 1) i *= 2,k++; int index1 = dp[l][k]; //dp记录的是下标 int index2 = dp[r-(1<<k)+1][k]; if (deep[index1] < deep[index2]) //返回deep小的那个下标 return index1; else return index2; } int LCA(int a,int b) { int temp; if (first[a] < first[b]) //找a和b第一次出现的位置,然后找小的位置到大的位置中间那一段的最小深度 temp = rmq.Query(first[a],first[b]); else temp = rmq.Query(first[b],first[a]); return Index[temp]; //返回的temp是deep数组的下标,通过index数组返回真实下标 } void Initial() { cnt = 0; memset(dist,0,sizeof(dist)); memset(visit,0,sizeof(visit)); memset(first,0,sizeof(first)); for (int i = 0; i <= n; ++i) tree[i].clear(); } void Dfs(int s,int dep,int dis) { visit[s] = 1; dist[s] = dis; //记录距离 Index[++cnt] = s; //cnt对应着真实的s结点 deep[cnt] = dep; //记录编号cnt的深度 if (!first[s]) first[s] = cnt; //s第一次出现的位置是cnt int size = tree[s].size(); for (int i = 0; i < size; ++i) { node cur = tree[s][i]; if (visit[cur.v]) continue; Dfs(cur.v,dep+1,dis+cur.len); Index[++cnt] = s; //回溯的时候也要更新 deep[cnt] = dep; } } int main() { int i,j,k,flag = 0; int a,b,c,z,fa; while (scanf("%d",&n) != EOF) { if (flag) printf("\n"); Initial(), flag = 1; for (i = 1; i < n; ++i) { scanf("%d%d%d",&a,&b,&c); cur.v = b,cur.len = c; tree[a].push_back(cur); cur.v = a,cur.len = c; tree[b].push_back(cur); } Dfs(0,0,0); rmq.Create(); scanf("%d",&q); for (i = 1; i <= q; ++i) { scanf("%d%d%d",&a,&b,&c); fa = LCA(a,b); ans = dist[a] + dist[b] - 2 * dist[fa]; fa = LCA(b,c); ans += dist[b] + dist[c] - 2 * dist[fa]; fa = LCA(a,c); ans += dist[a] + dist[c] - 2 * dist[fa]; printf("%d\n",ans / 2); } } }
本文ZeroClock原创,但可以转载,因为我们是兄弟。