LCA_最近公共祖先(离线)入门——POJ - 1330,HDU - 2586

4 篇文章 0 订阅

讲讲离线的做法,思想比较容易理解,代码也容易读实现,最近公共祖先顾名思义就是树上两个结点的公共祖先节点中层数最深的那个,有向树的树形是固定的,任意两点的LCA也是相同的,而若是无根树的话,以任意结点为根建树都会获得不同的情况,从而两点的lca也会不同,LCA一个简单应用就是求树上两点之间的最短距离,其值就是dis[a]+dis[b]-2*dis[lca(a,b)];

LCA的离线算法主要就是依靠并查集和dfs实现,先记录需要查询的结点对x,y,当dfs到需要查询的结点x时,若另一个结点y已经dfs过了,说明另一个结点y要嘛是x的祖先节点,要嘛是在他们的lca的另一棵子树中,每次一颗子树dfs完成后,就把子树的树根的并查集父节点设置为其父节点,所以对于这个子树的所有结点查询并查集祖先节点都是该树根的父节点。
这里写图片描述
如图,dfs每次都从树枝开始dfs,如果我们要查lca(6,10),那么让我们dfs到10时,结点6已经访问过了,因为以6为根的子树已经完全dfs完成,所以6,15,7结点查询并查集祖先都是4,lca(6,10)=F(6)=4;而如果我们要查询lca(6,1),当我们遍历到结点1时,以4为根的子树已经全部访问过了,所以对6查询并查集祖先F(6)就等于8了,所以lca(6,1)=F(6)=8;这就是lca离线的做法。具体实现见例题代码。

一般情况离线做法都是用另一个vector 数组来储存lca查询请求的。


Nearest Common Ancestors POJ - 1330

有向树找LCA,需要统计入度来找根,从根开始dfs并处理lca查询。

#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<vector>
#include<string>
#include<cmath>
#include<set>
#include<queue>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
const int mod = 1000000007;
const int maxm = 1005;
const int maxn = 40005;
int n, m ;
vector<int> e[maxn];
//vector<pair<int,int> > query[maxn];
//int dis[maxn];
bool vis[maxn];
int f[maxn];
int temp[maxn];
int beg, en;
int ans;
int F(int x){
    if (f[x] == x)return x;
    return f[x] = F(f[x]);
}

void findans(int cnt){
    vis[cnt] = 1;
    if (cnt == beg){ if (vis[en]){ ans = F(en); return; } }
    if (cnt == en){ if (vis[beg]){ ans = F(beg); return; } }
    for (int i = 0; i < e[cnt].size(); i++){
        int v = e[cnt][i];
        if (!vis[v]){
            findans(v);
            f[v] = cnt;
        }
    }
}

int main(){
    int t;
    int x, y, z;
    scanf("%d", &t);
    while (t--){
        scanf("%d", &n);
        memset(temp, 0, sizeof(temp));
        for (int i = 1; i <= n; i++){ f[i] = i; e[i].clear(); }
        for (int i = 1; i < n; i++){
            scanf("%d%d", &x, &y);
            e[x].push_back(y);
            temp[y]++;
        }
        scanf("%d%d", &beg, &en);
        memset(vis, 0, sizeof(vis));
        for (int i = 1; i <= n; i++){
            if (temp[i] == 0){ findans(i); break; }
        }
        printf("%d\n", ans);
    }
    return 0;
}

How far away ? HDU - 2586

两点的最近距离,dfs 的过程中同时处理出每点离根结点的距离,因为是无向图,以任意结点为根转有向树都行。

#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<vector>
#include<string>
#include<cmath>
#include<set>
#include<queue>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
const int mod = 1000000007;
const int maxm = 1005;
const int maxn = 40005;
int n, m ;
vector<pair<int,int> > e[maxn];
vector<pair<int,int> > query[maxn];
int ans[maxn][3];
int dis[maxn];
bool vis[maxn];
int f[maxn];

int F(int x){
    if (f[x] == x)return x;
    return f[x] = F(f[x]);
}

void findans(int cnt,int d){
    dis[cnt] = d;
    vis[cnt] = 1;
    for (int i = 0; i < query[cnt].size(); i++){
        pair<int,int> v = query[cnt][i];
        if (vis[v.first]){
            ans[v.second][2] = F(v.first);
        }
    }
    for (int i = 0; i < e[cnt].size(); i++){
        pair<int,int> v = e[cnt][i];
        if (!vis[v.first]){
            findans(v.first, d + v.second);
            f[v.first] = cnt;
        }
    }
}

int main(){
    int t;
    int x, y, z;
    scanf("%d", &t);
    while (t--){
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++){ f[i] = i; e[i].clear(); }
        for (int i = 1; i < n; i++){
            scanf("%d%d%d", &x, &y, &z);
            e[x].push_back({ y, z });
            e[y].push_back({ x, z });
        }
        for (int i = 0; i < m; i++){
            scanf("%d%d", &x, &y);
            query[x].push_back({ y, i });
            query[y].push_back({ x, i });
            ans[i][0] = x;
            ans[i][1] = y;
        }
        memset(vis, 0, sizeof(vis));
        memset(dis, 0x3f, sizeof dis);
        findans(1, 0);
        for (int i = 0; i < m; i++){
            printf("%d\n", dis[ans[i][0]] + dis[ans[i][1]] - 2 * dis[ans[i][2]]);
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值