LCA专题

标签(空格分隔): LCA


我的个人网站挂了,最近就先用这个来写博客吧。以后争取在这个网站写一些与OI无关的个人爱好的东西。


题目来源:code[VS]

倍增--在线算法

用 $f[i][j]$ 记录从 $i$ 向上跳 $2^j$ 次会跳到的位置。需 $O(nlog(n))$ 的预处理与 $O(mlog(n))$ 的查询。具体如下:

//code[VS]  P1036   LCA
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

struct  Edge
{
    int from,to,next;
    bool access;
    Edge(int form=0,int to=0,int next=0,bool access=true):from(from),to(to),next(next),access(access)   {}
}e[60100]; 

int depth[30100],f[30100][25],v[25],pre[30100];
int n;

int c[30100];
void bfs(int s)
{
    c[1] = s;   depth[s] = 1;
    int head = 1,tail = 1;
    while (head<=tail)
    {
        int x = c[head++];
        int v = pre[x];
        while (v)
        {
            if (e[v].access)
            {
                depth[e[v].to] = depth[x] + 1;
                c[++tail] = e[v].to;
                f[e[v].to][0] = x;
                e[v^1].access = false;
            }
            v = e[v].next;
        }
    }
}

void prepare()
{
    v[0] = 1;
    for (int j = 1; j<=20; j++)
        for (int i = 1; i<=n; i++)
        {
            f[i][j] = f[f[i][j-1]][j-1];
            v[j] = 2*v[j-1];
        }
}

int LCA(int x,int y)
{

    int ans=0;
    if (depth[x] < depth[y])    swap(x,y);

    for (int i = 20;depth[x]>depth[y];i--)
        if (depth[f[x][i]] >= depth[y])
        {
            ans += v[i];
            x = f[x][i];
        }

    if (x==y)   return ans;

    for (int i = 20; i>=0; i--)
        if (f[x][i] != f[y][i])
        {
            x = f[x][i];
            y = f[y][i];
            ans += v[i]*2;
        }
    ans += 2;
    return ans;
}

int main()
{
    memset(pre,0,sizeof(pre));

    scanf("%d",&n);
    for (int i = 1; i<n; i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        e[2*i] = Edge(x,y,pre[x],true);
        pre[x] = 2*i;
        e[2*i+1] = Edge(y,x,pre[y],true);
        pre[y] = 2*i+1;
    }

    bfs(1);

    prepare();

    int m;
    scanf("%d",&m);
    int x,y=1,ans=0;
    for (int i = 1; i<=m; i++)
    {
        x = y;
        scanf("%d",&y);
        ans += LCA(x,y);
    }   

    printf("%d",ans);
}

Tarjan--离线算法

对于这么一个玄学的算法,我不想说太多。。。用并查集进行维护,可以证明,每当搜到 $x$ 时,与之对应的 $y$ 所在集合的祖先一定为这两点的LCA。

//code[VS]  P1036   LCA
#include <cstdio>
#include <cstring>

struct Edge
{
    int from,to,next;
    bool access;
    Edge(int from=0,int to=0,int next=0,bool access=true):from(from),to(to),next(next),access(access)   {}
}e[60100];

struct Query
{
    int point,next;
    Query(int point=0,int next=0):point(point),next(next)   {}
}q[60100];

//fa[i]记录i的父亲,f[i]记录i指向的第一条边,fq[i]记录i指向的第一个查询
int fa[30100],f[30100],fq[30100],depth[30100];
int ans=0;      //记录答案
bool b[30100];

int c[30100];
void bfs(int s)     //通过广搜计算出深度与边的方向
{
    c[1] = s;   depth[s] = 1;
    int head = 1,tail = 1;
    while (head<=tail)
    {
        int x = c[head++];
        int v = f[x];
        while (v)
        {
            if (e[v].access)
            {
                e[v^1].access = false;          //将该边的反向边设为false
                depth[e[v].to] = depth[x] + 1;
                c[++tail] = e[v].to;
            }
            v = e[v].next;
        }
    }
}

int find(int x)
{
    return x==fa[x]?x:fa[x]=find(fa[x]);
}

void Union(int x,int y)
{
    int fy = find(y);
    fa[fy] = x;
}

void Tarjan_LCA(int x)
{
    fa[x] = x;          //以x创建一个集合
    int v = f[x];
    while (v)           //循环x的临边
    {
        if (e[v].access)    //如果该边为正方向(即指向儿子)
        {
            Tarjan_LCA(e[v].to);
            Union(x,e[v].to);       //将x的子树与x合并
        }
        v = e[v].next;
    }
    b[x] = true;    //设置该点已走过(必须在处理完儿子后设置,否则会有重复计算)

    v = fq[x];
    while (v)           //处理关于x点的查询
    {
        if (b[q[v].point])          //如果另一点已走过 花费=a点深度+b点深度-2*LCA(a,b)的深度
            ans = ans + ( depth[x] + depth[q[v].point] - 2*depth[find(q[v].point)] );
        v = q[v].next;
    }
}

int main()
{
    memset(b,false,sizeof(b));
    memset(f,0,sizeof(f));
    memset(fq,0,sizeof(fq));

    int n;
    scanf("%d",&n);
    for (int i = 1; i<n; i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        e[2*i] = Edge(x,y,f[x],true);   
        f[x] = 2*i;
        e[2*i+1] = Edge(y,x,f[y],true);
        f[y] = 2*i+1;
    }

    int m;
    scanf("%d",&m);
    int x,y=1;
    for (int i = 1; i<=m; i++)      //因为不知先查询到哪个点所以要存储双向变
    {
        x = y;
        scanf("%d",&y);
        q[i*2-1] = Query(y,fq[x]);
        fq[x] = 2*i-1;
        q[2*i] = Query(x,fq[y]);
        fq[y] = 2*i;
    }

    bfs(1);

    Tarjan_LCA(1);

    printf("%d",ans);
}

转载于:https://www.cnblogs.com/songer/p/6048389.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值