LCATarjan离线算法

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

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

#include
using namespace std;
#include
#include
#include
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]<return 0;
}

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

LCATarjan离线算法_第1张图片
这是题目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数组的取值是一个不断累加的过程。

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

你可能感兴趣的:(图论(各种树))