【hihoCoder第十七周】最近公共祖先·三

之前就写的是离线算法。思路就是先序一遍树,记录层数,然后高效RMQ就好。ST和线段树都能过。

以后有时间将之前的在线算法补上。

#include <bits/stdc++.h>

using namespace std;

#define MAXN 100005
#define MAXM 105
#define inf 0x7ffffff
int n;
struct Edge {
    int v, next;
} edge[MAXN];
int head[MAXN];
int e;

void addEdge(int u, int v) { //加边
    edge[e].v = v;
    edge[e].next = head[u];
    head[u] = e++;
}
int first[MAXN];//结点在搜索顺序数组中最先出现的位置(下标)
int occur[MAXN << 1]; //结点在出现的顺序数组重复的也要记录
int depth[MAXN << 1]; //结点在搜索树中的深度,与occur相对应
int dp_min[MAXN << 1][20]; //dp_min[i][j] 表示从第i个位置开始的2^j个元素中的最小值的下标
int m = 0; //不断记录出现的下标

void dfs(int u, int deep) {
    occur[++m] = u; //进入该点时进行记录
    depth[m] = deep;
    if(!first[u])
        first[u] = m;
    for(int i = head[u]; i + 1; i = edge[i].next) {
        dfs(edge[i].v, deep + 1);
        occur[++m] = u; //访问子树返回也要标记
        depth[m] = deep;
    }
}
void init() {
    memset(head, -1, sizeof(head));
    e = 0;
}

void RMQ_init(int num) {
    for(int i = 1; i <= num; i++)
        dp_min[i][0] = i; //注意dp_min存的不是最小值,而是最小值的下标
    for(int j = 1; j < 20; j++)
        for(int i = 1; i <= num; i++) {
            if(i + (1 << j) - 1 <= num) {
                dp_min[i][j] = depth[dp_min[i][j - 1]] < depth[dp_min[i + (1 << (j - 1))][j - 1]] ? dp_min[i][j - 1] : dp_min[i + (1 << (j - 1))][j - 1];
            }
        }
}

int RMQ_min(int a, int b) {
    int l = first[a], r = first[b]; //得到区间左右端点
    if(l > r) {
        int t = l;
        l = r;
        r = t;
    }
    int k = (int)(log(double(r - l + 1)) / log(2.0));
    int min_id = depth[dp_min[l][k]] < depth[dp_min[r - (1 << k) + 1][k]] ? dp_min[l][k] : dp_min[r - (1 << k) + 1][k]; //最小值下标
    return occur[min_id];//取得当前下标表示的结点
}

map<string, int> Hash_zh;
map<int, string> Hash_fa;

int main() {
    int t, a, b;
    init();
    m = 0;
    memset(first, 0, sizeof(first));
    bool in[MAXN];//记录结点有无入度
    memset(in, false, sizeof(in));
    int u = 0, v = 0, cnt = 1;
    string str_u, str_v;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) { //注意此题只有n-1条边
        cin >> str_u >> str_v;
        if (Hash_zh[str_u] == 0) {
            Hash_fa[cnt] = str_u;
            Hash_zh[str_u] = cnt ++;
        }
        if (Hash_zh[str_v] == 0) {
            Hash_fa[cnt] = str_v;
            Hash_zh[str_v] = cnt ++;
        }
        u = Hash_zh[str_u];
        v = Hash_zh[str_v];
        addEdge(u, v); //u->v单向
        //in[v] = true;
    }
    dfs(1, 0);
    RMQ_init(m);
    int op_n;
    scanf ("%d", &op_n);
    while (op_n --) {
        cin >> str_u >> str_v;
        if (str_u == str_v) {
            cout << str_u << endl;
            continue;
        }
        u = Hash_zh[str_u];
        v = Hash_zh[str_v];
        cout << Hash_fa[RMQ_min(u, v)] << endl;
    }

    return 0;
}
时间: 2024-10-09 23:54:53

【hihoCoder第十七周】最近公共祖先·三的相关文章

hihoCoder_#1069_最近公共祖先&#183;三(RMQ-ST模板)

#1069 : 最近公共祖先·三 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 上上回说到,小Hi和小Ho使用了Tarjan算法来优化了他们的"最近公共祖先"网站,但是很快这样一个离线算法就出现了问题:如果只有一个人提出了询问,那么小Hi和小Ho很难决定到底是针对这个询问就直接进行计算还是等待一定数量的询问一起计算.毕竟无论是一个询问还是很多个询问,使用离线算法都是只需要做一次深度优先搜索就可以了的. 那么问题就来了,如果每次计算都只针对一个询问进行的话

hiho一下 第十五周——最近公共祖先&#183;二(Trajan,离线LCA)

题目连接 http://hihocoder.com/problemset/problem/1067 题目大意 就是一棵树求任意两个节点的最近公共祖先. 算法描述 在题目的提示里面有比较详细的解释.这里就不多说了.这种算法的时间复杂度是O(n+q). 在算法的实现上也有一些技巧,在参考了一些代码后写了一个比较精简的Trajan_LAC算法. #include <bits/stdc++.h> using namespace std; typedef long long LL; const int

[hiho 17]最近公共祖先 三

题目描述 这次是使用在线算法解决这个问题. 两个节点的最近公共祖先就是这两个节点的通路上深度最浅的那个节点. 可以通过一遍深搜把树转成数组:每次经过一个节点(无论是从父节点进入还是从子节点返回)时,把它放入数组.同时要记录每个节点在数组中最后一次出现的位置. 使用RMQ-ST算法预先计算2^k长度区间内深度最浅的节点编号. 对于每次询问,将其转换为两个区间段求解. #include <iostream> #include <algorithm> #include <cstri

hihoCoder#1069 最近公共祖先&#183;三

原题地址 根据提示用Spase Table做 将Tree先展成List,因为数组长度等于边数的2倍,树中边数等于节点数-1,所以List数组只要开2倍节点数大小即可 WA了几次,原来是查询的时候出现左边界大于右边界的情况,所以这种情况要颠倒一下 代码: 1 #include <iostream> 2 #include <vector> 3 #include <string> 4 #include <map> 5 6 using namespace std;

hihoCoder week17 最近公共祖先&#183;三 lca st表

记录dfs序列,dfn[tot] 记录第tot次访问的节点 然后查两点在dfs序中出现的第一次 id[u] id[v] 然后  找 dep[k] = min( dep[i] ) {i 属于 [id[u], id[v]]} 最后dfn[k] 就是所求.. 感觉弄来弄去 就是 在映射... 无非就是 求一段序列深度最小的节点编号 #include <bits/stdc++.h> using namespace std; const int N = 2e5+10; int n, cnt, tot,

hihocoder1069最近公共祖先&#183;三(LCA在线算法--DFS+RMQ-ST)

树上任意两点的最近祖先,必定就是这两个节点的最短路径上深度最小的那个点. 例如:下图中,节点7和5,其最短路径为7--4--1--5, 这条路径上深度最小的点为节点1,其深度为1.节点1即为节点7和5的LCA. 因此,要找到任意两个节点的LCA,只需要先找到上述最短路径,再找到最短路径中深度最小的点.而这下面所述LCA在线算法所做的事. LCA在线算法描述(以上图为例): 1.获得“最短路径”(并不是真正的一条路径,包含其他节点,但不影响算法的正确性) 采用DFS遍历整棵树,得到以下数据: (1

最近公共祖先(三种算法)

最近研究了一下最近公共祖先算法,根据效率和实现方式不同可以分为基本算法.在线算法和离线算法.下面将结合hihocoder上的题目分别讲解这三种算法. 1.基本算法 对于最近公共祖先问题,最容易想到的算法就是从根开始遍历到两个查询的节点,然后记录下这两条路径,两条路径中距离根节点最远的节点就是所要求的公共祖先. 题目参见 #1062 : 最近公共祖先·一 附上AC代码,由于记录的方式采取的是儿子对应父亲,所以实现的时候有点小技巧,就是对第一个节点的路径进行标记,查找第二个节点的路径时一旦发现访问到

[ACM] hihocoder 1062 最近公共祖先&#183;一 (一般做法)

描述 小Ho最近发现了一个神奇的网站!虽然还不够像58同城那样神奇,但这个网站仍然让小Ho乐在其中,但这是为什么呢? "为什么呢?"小Hi如是问道,在他的观察中小Ho已经沉迷这个网站一周之久了,甚至连他心爱的树玩具都弃置一边. "嘿嘿,小Hi,你快过来看!"小Ho招呼道. "你看,在这个对话框里输入我的名字,在另一个对话框里,输入你的名字,再点这个查询按钮,就可以查出来--什么!我们居然有同一个祖祖祖祖祖爷爷?" "诶,真是诶--这个网

Solution: 最近公共祖先&#183;一 [hiho一下 第十三周]

题目1 : 最近公共祖先·一 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Ho最近发现了一个神奇的网站!虽然还不够像58同城那样神奇,但这个网站仍然让小Ho乐在其中,但这是为什么呢? “为什么呢?”小Hi如是问道,在他的观察中小Ho已经沉迷这个网站一周之久了,甚至连他心爱的树玩具都弃置一边. “嘿嘿,小Hi,你快过来看!”小Ho招呼道. “你看,在这个对话框里输入我的名字,在另一个对话框里,输入你的名字,再点这个查询按钮,就可以查出来……什么!我们居然有同一个