LCATarjan离线算法

来源:互联网 发布:mac 相册导入u盘 编辑:程序博客网 时间:2024/06/09 23:42

怎么说离线算法呢,我觉得就是说你把想要询问的东西提前存起来了,这样在遍历然后顺便建树的过程中就可以随时回答这个问题,然后再进行相应的更新…

http://www.cnblogs.com/JVxie/p/4854719.html
☝这个博主写得很不错,我就是这么看懂的。

#include<iostream>using namespace std;#include<vector>#include<algorithm>#include<string.h>vector<int>node[40010];//存相邻的点vector<int>value[40010];//存相邻的边权vector<int>query[40010];//存与i点相关的询问vector<int>num[40010];//存第几次询问int vis[40010];int f[40010];int dis[40010];//dis数组用来存每个点到根节点的距离,dis【i】表示i到根节点的距离int pr[210];//存答案int find(int x){    while (f[x] != x)        x = f[x];    return x;}void join(int x, int y){    int fx = find(x);    int fy = find(y);    if (f[fx] != fy)        f[fx] = fy;}void TarjanLCA(int root, int weight){    f[root] = root;    vis[root] = 1;    dis[root] = weight;    for (int i = 0; i < node[root].size(); i++)    {        int nextnode = node[root][i];        int nextw = value[root][i];        if (!vis[nextnode])        {            TarjanLCA(nextnode, weight + nextw);            join(nextnode, root);        }    }    for (int i = 0; i < query[root].size(); i++)    {        int neednode = query[root][i];        if (vis[neednode])            pr[num[root][i]] = dis[root] + dis[neednode] - 2 * dis[find(neednode)];    }}void init(int n){    memset(vis, 0, sizeof(vis));    memset(dis, 0, sizeof(dis));    memset(pr, 0, sizeof(pr));    for (int i = 1; i <= n; i++)    {        node[i].clear();        query[i].clear();        num[i].clear();        value[i].clear();    }}int main(){    int t;    cin >> t;    while (t--)    {        int n, m;        cin >> n >> m;        init(n);        for (int i = 0; i < n - 1; i++)        {            int u, v, w;            cin >> u >> v >> w;            node[u].push_back(v);            node[v].push_back(u);            value[u].push_back(w);            value[v].push_back(w);        }        for (int i = 0; i < m; i++)        {            int u, v;            cin >> u >> v;            query[u].push_back(v);//存下询问的两个点            query[v].push_back(u);            num[u].push_back(i);//存这是第几次询问了            num[v].push_back(i);        }        TarjanLCA(1, 0);        for (int i = 0; i < m; i++)            cout << pr[i]<<endl;    }    return 0;}

☝贴一份模板,带权值的,当然我觉得LCA核心还是找最近公共祖先,权值是一个相当于副产品。

这里写图片描述
这是题目http://101.200.220.237/contest/50/problem/294/

下面我自己模拟一下一个LCA的询问,time to 胡言乱语。

假设我们有一组数据 9个节点 8条边 联通情况如下:1–2,1–3,2–4,2–5,3–6,5–7,5–8,7–9
设我们要查找最近公共祖先的点为:9–8,4–6,7–5,5–3

下面我们开始跑程序。
输入结束后,1的子节点有2,3;2的子节点1,4,5;3的子节点有1,6;4的子节点有2;5的子节点有2,7,8;6的子节点有3;7的子节点有5,9;8的子节点有5;9的子节点有7;

询问的过程是8,9有关,4,6有关,7,5有关,5,3有关;

开始建树,这里的边权都是0,先不管,从1开始,Tarjan(1,0)。f【1】=1,1标记访问过,dis【1】=0;发现1有2个儿子,遍历,先是2,发现2没有被访问过,然后进入Tarjan(2,0),f【2】=2,2标记访问过,dis【2】=0;发现2有三个儿子,遍历,先是1,结果1被访问过,然后是4,发现4没有被访问过,进入Tarjan(4,0),f【4】=4,4标记被访问过,然后发现4有一个儿子2已经被访问过了,所以4寻找儿子的过程已经结束,进入询问,问6,结果6没有被访问过,所以结束询问,返回到Tarjan(4,0)执行完的地方,然后join(4,2),所以此时f【4】=2,继续走,进入2的第二个儿子5,发现没有被标记,进入Tarjan(5,0),f【5】=5,5被标记访问过,5有两个儿子,进入Tarjan(8,0),结束儿子访问,进入询问,与8有关的9还没有被标记,所以结束,join(8,5),所以f【8】=5,然后继续访问5的儿子7,进入Tarjan(7,0),f【7】=7,7标记被访问,发现7有一个未被标记的儿子9,然后进入Tarjan(9,0),f【9】=9,9标记为被访问,然后9没有有效儿子,进入询问,9和8有关系,发现8被标记过,那么8和9的最近公共祖先就是find(8)=5,因为这此时还是在这棵子树中,让我们继续走看看,然后结束询问join(9,7),此时f【9】=7,然后7的儿子都遍历结束,进入询问,7和5有关系,发现5已经被标记过了,所以7和5的最近公共祖先就是find(5)=5,询问结束,然后返回到join(7,5),此时f【7】=5,5的儿子遍历结束,然后就是询问,询问发现5和3有关系但是3还未被标记,所以继续向前走,join(5,2),此时f【5】=2,然后发现2的儿子访问结束,进入询问,发现没有询问,join(2,1),此时f【2】=1,此时继续访问1的另一个儿子3,f【3】=3,3标记为访问过,3有一个儿子6,进入Tarjan(6,0),f【6】=6,6标记为访问过,然后继续走发现6没有有效儿子,然后进入询问,询问发现4,然后4被标记,那么6和4的最近公共祖先就是find(4)=1,然后继续走join(6,3),此时f【6】=3,然后3对儿子的访问结束,进入询问,发现5被访问过,所以5和3的最近公共祖先就是find(5)=1,然后回到join(3,1),此时f【3】=1,然后发现对1的儿子的遍历结束,进入询问,发现没有询问,over!
所以我们的这些访问出结果的顺序是9-8,7-5,4-6,5-3,这其实就是利用的还是建树的过程,在逐步建树的过程中完成这些询问,非常机智!
边权的问题就是开一个dis数组,存每一个点到根节点的距离,然后最后减去两倍的dis【最近公共祖先】,dis数组的取值是一个不断累加的过程。

自己跑一遍程序就会好很多,而且我发现跑这种程序需要找一个合适大小的树,太大比较复杂,太小也不好,看着简单,但是体会不到算法的过程和巧妙之处!

原创粉丝点击