学习记录4:LCA (最近公共祖先)(hdu 2586 How far away?)

15 篇文章 0 订阅
8 篇文章 0 订阅

呃 个人理解 可能有偏差 = =

 

LCA (最近公共祖先) 是两个节点共有的祖先节点中深度最大的那个,可以说是离树根最远的祖先;

 

求LCA有几种算法 : RMQ  Tarjan 等等等

 

前提  DFS :

 对于每个节点

1.      先遍历子节点

2.      查询这个点的情况

3.      合并到他的父亲上

 

可以看出,我们在做DFS实际上尽可能的使高度小,因而两个点之间走过路径的最高点就是最近公共祖先。

 

RMQ

这个是先预先记录下DFS的顺序,然后如果要找U,V两个节点的LCA那么就查找这以两个点起始和终止的区间的最高点。以为我们DFS的顺序关系,可以知道两个结点区间的最高的那个点是他们所连接的路径的最高点也就是最近公共祖先.  因此可以用DP ,数据结构或其他方式去维护,查找某区间的最高点..

 

Tarjan

先将所有的查询输入保存,按照边DFS边查询的方法进行处理,用并查集实现节点合并;按照DFS关于边的访问情况来看,每条边都将会经过两次;这样一来,可以这样考虑,先将所有节点的ancestor先设为他自己,当他返回的时候合并到他的父节点上,将他和他的子辈的ancestor变为他的父亲,当遍历到某个点A时,判断和这个点相关的查询,如果访问过要查询的节点B,那么find(B).ancestor就是结果,若未访问过B,则在此点得不出结果;

 

举个例子,假设当前我们DFS到达5号点,我们可以查看与5号点相关的询问,判断是否能处理,假设当前点的相关查询是(5, 2)和(5, 6) ;如果在与这个点连接的要查询的另一个点是访问过的,那么那个点的ancestor就是他们的最近公共祖先;

对于 (5, 2) 这个查询,2号点是经过的,那么ans(5, 2) = find(2).ancestor ;

而对于(5,  6), 因为6号点还未经过,所以不能判断这种情况。但是换个角度,当我们在6号询问5号点时,5号点是访问过的,这样一来,同样能够得到答案,因此,在存储边的关系时,保存两个方向;

 

再是为什么可以以这种方式得到结果:

 

我们可以分为两种情况:

1.  如果两个点在以根为起始点的同一条链上,那么高的那个点就是答案

2.  如果两个点不在以跟为起始点的同一条链上时,那么我们可以对这棵树做一些抽象处理,只保留这棵树分别到这两个节点的两条链,我们会发现处理过的这棵树有且只有一个分叉,而连接这个分叉的点就是这两个点最近公共祖先;

 

嗯 还可以这样看

 

对一个节点A,先遍历子节点,在节点A返回时,合并到他的父节点F上,(用并查集把这个节点和他的子辈全合并上去)然后到达的是这个节点的兄弟B(如果有的话),如果要把这个节点A及节点B相连,一定会通过他们的父节点F,那么他们的LCA就是F;再往下推,假设A的孩子要和B相连,一定会通过A,同样要通过F..以此类推,可以发现,当LCA是F时,两个点恰好是在F的不同的子树上,只要在搜索一棵子树C1在他返回时把他的ancestor标记为F;那么,在F的另一棵子树C2上的某点要查询的点恰好是C1上的点K,很可以得出答案是F , 而若是点K是F父节点或父节点之上的点时,因为还未返回,所以ancestor还是K, 答案也是正确的,这样看来可以与DFS顺序达成一致…


如果将这个单元作为一个子树看成C1,那么对更大的范围的树也是成立的;(DFS也是先处理子树的小范围,再扩展,所以,默认的顺序就是最近公共祖先优先..符合顺序要求,并且这种到点查询的方式,不会使后面的结果受影响)


呃..表达能力有限 - -


下面这个例子是  hdu 2586 


How far away ?

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 12481    Accepted Submission(s): 4607


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
 

嗯, 题目意思是求任意两个点之间的最短距离

思路  ANS = 节点1到根的距离 + 节点2到根的距离 - 2 * 两个点的最近公共祖先到根节点的距离

于是 这样就转换成求LCA的问题了  至于每个节点到根节点的距离  边DFS边保存就行了


#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cctype>
#include<cstdio>
#include<string>
#include<cstring>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<bitset>
#define pi acos(-1.0)
#define inf 1<<29
#define INF 0x3f3f3f3f
#define zero 1e-8

const int li[] = { -1, 0, 1, 0};
const int lj[] = {0, -1, 0, 1};

using namespace std;
const int N = 4e4 + 7;

vector<int> edge[N];
vector<int> len[N];
bool flag[N];
int root[N];
int nodenum[N];

struct node {
    int a, b, id;
    int ans;
} arr[N];
int arrnum;

int Root(int n)
{
    return  root[n] == -1 ? n : (root[n] = Root(root[n]));
}

inline void Merge(int a, int b)
{
    int ra = Root(a);
    int rb = Root(b);
    root[rb] = ra;
}


bool cmp(const node &a, const node &b)
{
    if (a.id < b.id) return true;
    else if (a.id == b.id) {
        return a.ans > b.ans;
    }
    return false;
}

int m, n;
void init()
{

    for (int i = 0; i < n + 5; ++i) {
        edge[i].clear();
        len[i].clear();
        root[i] = -1;
    }
    memset(flag, 0, sizeof(flag));
    arrnum = 0;

}

void dfs(int node, int lens, int tot)
{

    nodenum[node] = tot + lens;

   //这一段可以改成和node节点相关点的情况 不用全部搜索
    for (int i = 0; i < arrnum; ++i) {
        if (arr[i].a == node && flag[arr[i].b]) {
            int rb = Root(arr[i].b);
            arr[i].ans = nodenum[arr[i].a] + nodenum[arr[i].b] - 2 * nodenum[rb];
        }
    }

    for (int i = 0; i < edge[node].size(); ++i) {
        if (flag[edge[node][i]]) continue;
        flag[edge[node][i]] = true;
        dfs(edge[node][i], len[node][i], tot + lens);
        Merge(node, edge[node][i]);
    }


}
int main()
{
    int t;
    scanf("%d", &t);
    while(t--) {

        scanf("%d %d", &n, &m);
        init();
        for (int i = 0; i < n - 1; ++i) {
            int a, b, c;
            scanf("%d %d %d", &a, &b, &c);
            edge[a].push_back(b);
            edge[b].push_back(a);
            len[a].push_back(c);
            len[b].push_back(c);
        }

        for (int i = 0; i < m; ++i) {
            int l, r;
            scanf("%d %d", &l, &r);
            arr[arrnum].a = l;
            arr[arrnum].b = r;
            arr[arrnum].ans = 0;
            arr[arrnum++].id = i + 1;

            arr[arrnum].a = r;
            arr[arrnum].b = l;
            arr[arrnum].ans = 0;
            arr[arrnum++].id = i + 1;

        }

        flag[1] = true;
        dfs(1, 0, 0);

        sort(arr, arr + arrnum, cmp);
        int ID = -1;
        for (int i = 0; i < arrnum; ++i) {
            if (arr[i].id == ID) continue;
            printf("%d\n", arr[i].ans);
            ID = arr[i].id;
        }
    }

    return 0;
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值