hdu2586(lca模板 / tarjan离线 + RMQ在线)

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

 

题意: 给出一棵 n 个节点的带边权的树, 有 m 个形如 x y 的询问, 要求输出所有 x, y节点之间的最短距离.

 

思路: dis[i] 存储 i 节点到根节点的最短距离, lca 为 x, y 的最近公共祖先, 那么 x, y 之间的最短距离为: dis[x] + dis[y] - 2 * dis[lca] .

 

解法1: tarjan离线算法

关于该算法

 1 Tarjan(u)//marge和find为并查集合并函数和查找函数
 2 {
 3     for each(u,v)    //访问所有u子节点v
 4     {
 5         Tarjan(v);        //继续往下遍历
 6         marge(u,v);    //合并v到u上
 7         标记v被访问过;
 8     }
 9     for each(u,e)    //访问所有和u有询问关系的e
10     {
11         如果e被访问过;
12         u,e的最近公共祖先为find(e);
13     }
14 }

详解见: http://www.cnblogs.com/ECJTUACM-873284962/p/6613379.html

 

代码:

 1 #include <iostream>
 2 #include <stdio.h>
 3 #include <string.h>
 4 using namespace std;
 5 
 6 const int MAXN = 4e4 + 10;
 7 struct node{
 8     int u, v, w, lca, next;
 9 }edge1[MAXN << 1], edge2[810];//edge1记录树, edge2记录询问
10 
11 int vis[MAXN], pre[MAXN], dis[MAXN];//vis[i]标记i是否已经搜索过, pre[i]记录i的根节点, dis[i]记录i到根节点的距离
12 int head1[MAXN], head2[MAXN], ance[MAXN], ip1, ip2;
13 
14 void init(void){
15     memset(vis, 0, sizeof(vis));
16     memset(dis, 0, sizeof(dis));
17     memset(head1, -1, sizeof(head1));
18     memset(head2, -1, sizeof(head2));
19     ip1 = ip2 = 0;
20 }
21 
22 void addedge1(int u, int v, int w){//前向星
23     edge1[ip1].v = v;
24     edge1[ip1].w = w;
25     edge1[ip1].next = head1[u];
26     head1[u] = ip1++;
27 }
28 
29 void addedge2(int u, int v){
30     edge2[ip2].u = u;
31     edge2[ip2].v = v;
32     edge2[ip2].lca = -1;
33     edge2[ip2].next = head2[u];
34     head2[u] = ip2++;
35 }
36 
37 int find(int x){
38     return pre[x] == x ? x : pre[x] = find(pre[x]);
39 }
40 
41 void jion(int x, int y){
42     x = find(x);
43     y = find(y);
44     if(x != y) pre[y] = x;
45 }
46 
47 void tarjan(int u){
48     vis[u] = 1;
49     ance[u] = pre[u] = u;
50     for(int i = head1[u]; i != -1; i = edge1[i].next){
51         int v = edge1[i].v;
52         int w = edge1[i].w;
53         if(!vis[v]){
54             dis[v] = dis[u] + w;
55             tarjan(v);
56             jion(u, v);
57         }
58     }
59     for(int i = head2[u]; i != -1; i = edge2[i].next){
60         int v = edge2[i].v;
61         if(vis[v]) edge2[i].lca = edge2[i ^ 1].lca = ance[find(v)];
62     }
63 }
64 
65 int main(void){
66     int t, n, m, x, y, z;
67     scanf("%d", &t);
68     while(t--){
69         init();
70         scanf("%d%d", &n, &m);
71         for(int i = 1; i < n; i++){
72             scanf("%d%d%d", &x, &y, &z);
73             addedge1(x, y, z);
74             addedge1(y, x, z);
75         }
76         for(int i = 0; i < m; i++){
77             scanf("%d%d", &x, &y);
78             addedge2(x, y);
79             addedge2(y, x);
80         }
81         dis[1] = 0;
82         tarjan(1);
83         for(int i = 0; i < m; i++){
84             int cc = i << 1;
85             int u = edge2[cc].u;
86             int v = edge2[cc].v;
87             int lca = edge2[cc].lca;
88             printf("%d\n", dis[u] + dis[v] - 2 * dis[lca]);
89         }
90     }
91     return 0;
92 }
View Code

 

解法2: lca转RMQ

关于该算法

ver[] 存储树的 dfs 路径

first[u] 为顶点 u 在 ver 数组中第一次出现时的下标

deep[indx] 为顶点 ver[indx] 的深度

对于求 x, y 的 lca, 先令 l = first[x], r = first[y], 即 l, r 分别为 x, y 第一次在 ver 数组中出现时对应的下标

在 deep[] 数组中找到区间 [l, r] 中的最小值, 其下标对应的 ver 值即为 x, y 的 lca. (区间最值可以用 RMQ 处理)

详解见: http://blog.csdn.net/u013076044/article/details/41870751

 

代码:

 1 #include <iostream>
 2 #include <stdio.h>
 3 #include <string.h>
 4 #include <math.h>
 5 using namespace std;
 6 
 7 const int MAXN = 4e4 + 10;
 8 struct node{
 9     int v, w, next;
10 }edge[MAXN << 1];
11 
12 int dp[MAXN << 1][30]; //dp[i][j]存储deep数组中下标i开始长度为2^j的子串中最小值的下标
13 int first[MAXN], ver[MAXN << 1], deep[MAXN << 1];
14 int vis[MAXN], head[MAXN], dis[MAXN], ip, indx;
15 
16 inline void init(void){
17     memset(vis, 0, sizeof(vis));
18     memset(head, -1, sizeof(head));
19     ip = 0;
20     indx = 0;
21 }
22 
23 void addedge(int u, int v, int w){
24     edge[ip].v = v;
25     edge[ip].w = w;
26     edge[ip].next = head[u];
27     head[u] = ip++;
28 }
29 
30 void dfs(int u, int h){
31     vis[u] = 1; //标记已搜索过的点
32     ver[++indx] = u; //记录dfs路径
33     first[u] = indx; //记录顶点u第一次出现时对应的ver数组的下标
34     deep[indx] = h; //记录ver数组中对应下标的点的深度
35     for(int i = head[u]; i != -1; i = edge[i].next){
36         int v = edge[i].v;
37         if(!vis[v]){
38             dis[v] = dis[u] + edge[i].w;
39             dfs(v, h + 1);
40             ver[++indx] = u;
41             deep[indx] = h;
42         }
43     }
44 }
45 
46 void ST(int n){
47     for(int i = 1; i <= n; i++){
48         dp[i][0] = i;
49     }
50     for(int j = 1; (1 << j) <= n; j++){
51         for(int i = 1; i + (1 << j) - 1 <= n; i++){
52             int x = dp[i][j - 1], y = dp[i + (1 << (j - 1))][j - 1];
53             dp[i][j] = deep[x] < deep[y] ? x : y;
54         }
55     }
56 }
57 
58 int RMQ(int l, int r){
59     int len = log2(r - l + 1);
60     int x = dp[l][len], y = dp[r - (1 << len) + 1][len];
61     return deep[x] < deep[y] ? x : y;
62 }
63 
64 int LCA(int x, int y){
65     int l = first[x], r = first[y];
66     if(l > r) swap(l, r);
67     int pos = RMQ(l, r);
68     return ver[pos];
69 }
70 
71 int main(void){
72     int t, n, m, x, y, z;
73     scanf("%d", &t);
74     while(t--){
75         init();
76         scanf("%d%d", &n, &m);
77         for(int i = 1; i < n; i++){
78             scanf("%d%d%d", &x, &y, &z);
79             addedge(x, y, z);
80             addedge(y, x, z);
81         }
82         dis[1] = 0;
83         dfs(1, 1);
84         ST(2 * n - 1);
85         for(int i = 0; i < m; i++){
86             scanf("%d%d", &x, &y);
87             int lca = LCA(x, y);
88             printf("%d\n", dis[x] + dis[y] - 2 * dis[lca]);
89         }
90     }
91     return 0;
92 }
View Code

 

posted @ 2017-07-18 21:08  geloutingyu  阅读(279)  评论(0编辑  收藏  举报