最近公共祖先 - 离线处理

hiho1067

题目链接

问树上两个点的最近公共祖先

-------------------------------------------------------------------------------------------------------

第一次写所谓的离线算法,不是来一个询问就处理一下,而是在扫描树的过程中每扫到一个点就处理和这个点相关的询问。

因为这个点的祖先肯定都访问过一次了,且所有标记为1的点都是这个点的祖先

第一次扫到这个点时标记为1,扫完这个点的所有子节点后标记为2;

关于当前点a的询问有三种情况:

1.另外一个点b的标记为0,暂时无法判断;

2.另外一个点b的标记为1,说明b为a的祖先,输出b;

3.另外一个点b的标记为2,找到离b最近的标记为1的点;

关于3,采用并查集维护,开始时每个点的[最近标记为1的点]都设成自己。从叶子向上,每标记一个点为2时,就把他及他的子孙挂到他的父节点上。

注意:扫到一个点的时候一定要先处理和它相关的询问,然后再dfs这个节点,如果先dfs这个节点,再处理询问会出错。因为这个时候他的标记变成2了,他已经挂到父节点上了,如果询问他及他的子孙节点的最近祖先应该返回他,但是这时会返回他的父亲。

#include <set>
#include <map>
#include <stack>
#include <queue>
#include <cmath>
#include <vector>
#include <string>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>

#define MAX(a,b) ((a)>=(b)?(a):(b))
#define MIN(a,b) ((a)<=(b)?(a):(b))
#define OO 0x0fffffff
using namespace std;
typedef long long LL;
const int N = 100100;
struct Edge{ int to,next,id; };
int father[N];
int ans[N],vis[N];
int headsq[N],headst[N];
Edge tree[N*2],query[N*2];
int eidq = 0;
int eidt = 0;
void addQuery(int a,int b,int id){
    query[eidq].to = b;
    query[eidq].next = headsq[a];
    query[eidq].id = id;
    headsq[a] = eidq++;
    query[eidq].to = a;
    query[eidq].next = headsq[b];
    query[eidq].id = id;
    headsq[b] = eidq++;
    ans[id]=-1;
}
void addEdge(int a,int b){
    tree[eidt].to = b;
    tree[eidt].next = headst[a];
    headst[a] = eidt++;
}
int find(int id){
    int fid = father[id];
    if(fid==id) return fid;
    return (father[id] = find(fid));
}
void dfs(int id,int fid){
    for(int curId=headst[id];curId!=-1;curId=tree[curId].next){
        int to = tree[curId].to;
        vis[to] = 1;
        for(int qid=headsq[to];qid!=-1;qid=query[qid].next){
            if(ans[query[qid].id]!=-1) continue;
            int curq = query[qid].to;
            if(vis[curq]==1) {
                ans[query[qid].id]=curq;
            }
            else if(vis[curq]==2){
                ans[query[qid].id]=find(curq);
            }
        }
        dfs(to,id);
    }
    vis[id] = 2;
    father[id] = father[fid];
}


int n,m;
int id1,id2;
map<string,int> m1;
map<int,string> m2;
char name1[64],name2[64];
int main(){
    memset(vis,0,sizeof(vis));
    memset(headsq,-1,sizeof(headsq));
    memset(headst,-1,sizeof(headst));

    cin>>n;
    int cnt = 0;
    for(int i=0;i<n;i++){
        cin>>name1>>name2;
        if(!(m1.count(name1))){
            m1[name1] = cnt;
            m2[cnt] = name1;
            id1 = cnt++;
        }
        else id1 = m1[name1];
        if(!(m1.count(name2))){
            m1[name2] = cnt;
            m2[cnt] = name2;
            id2 = cnt++;
        }
        else id2 = m1[name2];
        addEdge(id1,id2);
    }
    cin>>m;
    for(int i=0;i<m;i++){
        cin>>name1>>name2;
        addQuery(m1[name1],m1[name2],i);
    }

    for(int i=0;i<=n;i++) father[i]=i;

    vis[0]=1;
    dfs(0,0);

    for(int i=0;i<m;i++){
        cout<<m2[ans[i]]<<endl;
    };

    return 0;
}

 

转载于:https://www.cnblogs.com/redips-l/p/6913997.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值