【HDU 2586 How far away?】LCA问题 Tarjan算法

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

题意:给出一棵n个节点的无根树,每条边有各自的权值。给出m个查询,对于每条查询返回节点u到v的最短路径的权值和,按查询顺序输出结果。

数据范围:n [2, 40000], m[1, 200]

思路:Tarjan算法:dfs遍历每个点,每遍历完 r 的一个孩子 c, 把 c 并入以 r 为祖先的集合,并处理 c 的所有查询 q:若qi的目标节点 v 已被遍历到,那么一定有lca(c, v) = find(v)。

具体实现上,需要记录这样几个信息:每个节点的深度depth、邻接表G、访问标记vis。

对于每组查询(u, v)时,可用这个公式得到结果res(u, v) = depth(u) - depth(lca(u,v)) + depth(v) - depth(lca(u,v))。

注意Tarjan算法是脱机的(离线的)、批处理的,即其查询结果的生成顺序与最初的查询输入顺序无关。因此需要将乱序生成的结果合理组织以实现顺序输出。这一点我没有想好怎么做,下面的实现是参照了到网上他人的代码。具体方法就是:用类似记录查询目标节点的方法去记录查询序号,即query[r][i]存的是第r个节点的第i个查询所涉及的目标节点,类似地,num[r][i]存的是第r个节点的第i个查询在输入查询序列中的序号。再附加一个ans[i]存储第i条查询的结果。这样每处理一个查询,即可把结果存入ans[num[r][i]] = res(r, i)

p.s 注意题目描述:没有说节点的序号分布在1...n,而我们的vis, query, depth等数组都是以下标标识节点号访问的。所以每次初始化时注意覆盖到整个区间[1, MAX_N]。这题n<=40000不是很大,若超过数组能开的最大长度,则需要“离散化”。

还有一点就是,这个问题只要使用深度优先遍历策略,左根右三个节点的访问顺序是没有关系的。因此可以借助先序遍历先访问根节点的便利,先修改vis数组,后遍历其邻接表,从而避免因邻接表存了双向边而出现“兜圈子”的情况。

  1 #include <cstdio>
  2 #include <vector>
  3 #include <cstring>
  4 using namespace std;
  5
  6 const int MAX_N = 40005;
  7 const int MAX_M = 205;
  8
  9 int T;
 10 int n, m;
 11 struct Edge{
 12     int to, cost;
 13     Edge(){}
 14     Edge(int t, int c):to(t), cost(c){}
 15 };
 16 vector<Edge> G[MAX_N];
 17 vector<int> query[MAX_N];
 18 int vis[MAX_N];
 19 vector<int> num[MAX_N];
 20 int par[MAX_N];
 21 int ans[MAX_M];
 22 int depth[MAX_N];
 23
 24 void init(){
 25     memset(vis, 0, sizeof(vis));
 26     memset(ans, 0, sizeof(ans));
 27     memset(depth, 0, sizeof(depth));
 28     for(int i=0; i<MAX_N; i++){
 29         par[i] = i;
 30         G[i].clear();
 31         num[i].clear();
 32         query[i].clear();
 33     }
 34 }
 35 int find(int x){
 36     if(par[x] == x) return x;
 37     return par[x] = find(par[x]);
 38 }
 39 void unite(int x, int y){
 40     x = find(x);
 41     y = find(y);
 42     if(x == y) return ;
 43     par[y] = x;
 44 }
 45
 46 void dfs(int r, int l){
 47     vis[r] = 1;//先序遍历
 48     depth[r] = l;
 49     for(int i=0; i<G[r].size(); i++){
 50         if(!vis[G[r][i].to]){
 51             dfs(G[r][i].to, l+G[r][i].cost);
 52             unite(r, G[r][i].to);
 53         }
 54     }
 55     for(int i=0; i<query[r].size(); i++){
 56         if(vis[query[r][i]]){//r的第i个查询的目标节点
 57             int ca = find(query[r][i]);
 58             ans[num[r][i]] = depth[r] + depth[query[r][i]] - depth[ca] - depth[ca];
 59         //r的第i个查询所持有的查询号
 60         }
 61     }
 62 }
 63
 64 void lca(int r){
 65     dfs(r, 0);
 66 }
 67
 68 int main()
 69 {
 70     //freopen("2586.txt", "r", stdin);
 71     scanf("%d", &T);
 72     while(T--){
 73         init();
 74         scanf("%d%d", &n, &m);
 75         for(int i=0; i<n-1; i++){
 76             int u, v, c;
 77             scanf("%d%d%d", &u, &v, &c);
 78             G[u].push_back(Edge(v, c));
 79             G[v].push_back(Edge(u, c));
 80         }
 81         for(int i=0; i<m; i++){
 82             int u, v;
 83             scanf("%d%d", &u, &v);
 84             query[u].push_back(v);
 85             query[v].push_back(u);
 86             num[u].push_back(i);//u的下一个查询号插入向量num[u]
 87             num[v].push_back(i);
 88         }
 89         lca(1);
 90         for(int i=0; i<m; i++){
 91             printf("%d\n", ans[i]);
 92         }
 93 //        for(int i=1; i<=n; i++){
 94 //            for(int j=0; j<G[i].size(); j++){
 95 //                printf("%d %d %d\n", i, G[i][j].to, G[i][j].cost);
 96 //            }
 97 //        }
 98     }
 99     return 0;
100 }
时间: 2024-11-02 22:22:13

【HDU 2586 How far away?】LCA问题 Tarjan算法的相关文章

【HDU 4547 CD操作】LCA问题 Tarjan算法

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4547 题意:模拟DOS下的cd命令,给出n个节点的目录树以及m次查询,每个查询包含一个当前目录cur和一个目标目录tar,返回从cur切换到tar所要使用的cd命令次数: 注意这里的cd命令是简化版,只能进行如下两种操作: 1. cd   ..                                        //返回父目录 2. cd   cur\一系列目录\tar          

【POJ 1330 Nearest Common Ancestors】LCA问题 Tarjan算法

题目链接:http://poj.org/problem?id=1330 题意:给定一个n个节点的有根树,以及树中的两个节点u,v,求u,v的最近公共祖先. 数据范围:n [2, 10000] 思路:从树根出发进行后序深度优先遍历,设置vis数组实时记录是否已被访问. 每遍历完一棵子树r,把它并入以r的父节点p为代表元的集合.这时判断p是不是所要求的u, v节点之一,如果r==u,且v已访问过,则lca(u, v)必为v所属集合的代表元.p==v的情况类似. 我的第一道LCA问题的Tarjan算法

【POJ 1236 Network of Schools】强联通分量问题 Tarjan算法,缩点

题目链接:http://poj.org/problem?id=1236 题意:给定一个表示n所学校网络连通关系的有向图.现要通过网络分发软件,规则是:若顶点u,v存在通路,发给u,则v可以通过网络从u接收到. 现要求解两个问题: TaskA: 最少分发给几个学校,就可以使所有的学校都能得到软件. TaskB: 最少增加几条边,就可以使得,发软件给任一学校,所有学校都可以收到. 思路:先进行强联通分量分解,然后缩点,并计算缩点后每个点的出度.入度.入度为0的点的总数为 a ,出度为0的点总数为 b

poj 1330 Nearest Common Ancestors

点击打开链接poj 1330 思路:LCA+离线Tarjan算法 分析: 1 LCA用于找到一棵树或者一个图中找到两个节点的最近的祖先问题. 2算法:    1: Tarjan算法基于深度优先搜索的框架,对于新搜索到的一个结点,首先创建由这个结点构成的集合    2: 再对当前结点的每一个子树进行搜索,每搜索完一棵子树,则可确定子树内的LCA询问都已解决.    3: 其他的LCA询问的结果必然在这个子树之外,这时把子树所形成的集合与当前结点的集合合并,并将当前结点设为这个集合的祖先.    4

LCA

                     LCA是用来求解一棵树或图中两点的最近公共祖先 LCA(Least Common Ancestor),顾名思义,是指在一棵树中,距离两个点最近的两者的公共节点.也就是说,在两个点通往根的道路上,肯定会有公共的节点,我们就是要求找到公共的节点中,深度尽量深的点.还可以表示成另一种说法,就是如果把树看成是一个图,这找到这两个点中的最短距离. 1:如果读入的树,那么应该有固定的根节点我们只要对根节点LCA即可. 2:如果读入的是无向图,那么选择1作为根节点进行

算法:poj 1523 SPF(tarjan求割点)

题意 给一个连通的无向图,求这个图的所有割点,并且输出各个割点和相连的边去掉之后,会变成几 个连通分量 思路 用tarjan求割点的基础题,要求对tarjan算法的原理真正搞懂,这题就水了. 代码 /**===================================================== * This is a solution for ACM/ICPC problem * * @source : poj-1523 SPF * @description : 图的连通性-ta

UVAoj 11324 - The Largest Clique(tarjan + dp)

题意:给定一个有向图,寻找一个点数最大集合,使得这个集合中的任意两个点u,v, 都有u->v 或者 v->u 或者u<==>v 思路:首先将强连通分量通过tarjan算法求出来,然后进行缩点,也就是每一个缩点所组成的图就是一个DAG图!令每一个点的权值就是这个缩点所包含节点(也就是对应的强连通分量的节点数目),因为强连通分量的任意的两个节点都是相互可达的,那么这个 缩点要么选要么不选,问题就转换成了DAG图上的最长路径! #include<iostream> #incl

hdu Caocao&#039;s Bridges(无向图边双连通分量,找出权值最小的桥)

/* 题意:给出一个无向图,去掉一条权值最小边,使这个无向图不再连同! tm太坑了... 1,如果这个无向图开始就是一个非连通图,直接输出0 2,重边(两个节点存在多条边, 权值不一样) 3,如果找到了桥的最小权值为0,也就是桥上的士兵数为0,那么还是要最少派一个 士兵过去炸掉桥! 思路:假设每两个节点最多只有一条边进行相连! 进行tarjan算法,如果该算法调用了超过2次,说明这个原图就是不连通的! 否则在tarjan算法中将桥存起来!然后我们遍历每一座桥,看一看我们找到的 桥(连接的两个定点

HDOJ(HDU) 2109 Fighting for HDU(简单排序比较)

Problem Description 在上一回,我们让你猜测海东集团用地的形状,你猜对了吗?不管结果如何,都没关系,下面我继续向大家讲解海东集团的发展情况: 在最初的两年里,HDU发展非常迅速,综合各种ACM算法生成的老鼠药效果奇好,据说该药专对老鼠有效,如果被人误食了,没有任何副作用,甚至有传闻说还有健胃的效果,不过这倒没有得到临床验证.所以,公司的销量逐年递增,利润也是节节攀升,作为股东之一的公主负责财务,最近半年,她实在辛苦,多次因为点钞票造成双手抽筋而住院,现在在她面前你根本不要提到"