首页 > 其他分享 >浅谈关于LCA

浅谈关于LCA

时间:2023-10-06 09:11:23浏览次数:35  
标签:浅谈 dep ll st fa 关于 LCA rl inline

prologue

本身只会 tarjan 和 倍增法求LCA 的,但在发现有一种神奇的\(O(1)\) 查询 lca 的方法,时间优化很明显。


main body


倍增法

先讨论倍增法,倍增法求 lca 是一种很常见普遍的方法,这里直接放代码了,其本身的内核就是让较低点每次都跳 $ 2 ^ k $ 步,如果跳的比另一个高了,就不跳那么高,跳 \(2 ^ {k-1}\) 步,这就用对数级的复杂度求出来了 LCA。

code

比较基础,建议直接背过。

inline void bfs()
{
    memset(dep, 0x3f, sizeof dep);

    ll hh = 0, tt = -1;

    q[++ tt] = 1;
    
    dep[0] = 0, dep[1] = 1;

    while(hh <= tt)
    {
        ll u = q[hh ++ ];

        for(rl i = h[u]; ~i; i = ne[i])
        {
            ll v = e[i];
            if(dep[v] > dep[u] + 1)
            {
                dep[v] = dep[u] + 1;
                fa[v][0] = u;
                q[++ tt] = v;
                for(rl k=1; k <= 20; ++ k)
                    fa[v][k] = fa[fa[v][k - 1]][k - 1];
            }
        }
    }
}

inline ll lca(ll a, ll b)
{
    if(dep[a] < dep[b]) swap(a, b);

    for(rl k=20; k >= 0; -- k)
        if(dep[fa[a][k]] >= dep[b])
            a = fa[a][k];

    if(a == b) return a;

    for(rl k=20; k >= 0; -- k)
        if(fa[a][k] != fa[b][k])
            a = fa[a][k], b = fa[b][k];

    return fa[a][0];
}

tarjan求LCA

这是一种离线做法(将所有的询问存下来,然后再一一输出)。

这种做法我自我感觉不太好用,但是因为这种做法的时间复杂度是 \(O(n + m)\) 的,所以说有的人用起来很香(个人不喜欢,主要是没怎么敲过/

#include <bits/stdc++.h>
using namespace std;
#define ll int 
#define rl register ll

const ll N = 20010, M = 2 * N;

ll n, m;

ll tot, ne[M], e[M], h[N], w[M];

ll p[N], dis[N], st[N];

ll res[N];

vector<pair<int, int>> query[N];

inline void add(ll a, ll b, ll c)
{
    ne[++tot] = h[a], h[a] = tot, e[tot] = b, w[tot] = c;
}

inline void dfs(ll u, ll fa)
{
    for(rl i=h[u]; ~i; i = ne[i])
    {
        ll v = e[i];
        if(v == fa) continue;
        dis[v] = dis[u] + w[i];
        dfs(v, u);
    }
}

inline ll find(ll x)
{
    if(p[x] == x) return x;
    else return p[x] = find(p[x]);
}

inline void tarjan(ll u)
{
    st[u] = 1;
    for(rl i=h[u]; ~i; i = ne[i])
    {
        ll v = e[i];
        if(!st[v])
        {
            tarjan(v);
            p[v] = u;
        }
    }
    
    for(auto item : query[u])
    {
        ll y = item.first, id = item.second;
        if(st[y] == 2)
        {
            ll anc = find(y);
            res[id] = dis[y] + dis[u] - 2 * dis[anc];
        }
    }
    
    st[u] = 2;
}

int main()
{
    cin >> n >> m;
    
    memset(h, -1, sizeof h);
    
    for(rl i=1; i <= n - 1; ++ i)
    {
        ll a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }
    
    for(rl i=1; i <= m; ++ i)
    {
        ll a, b;
        cin >> a >> b;
        if(a != b)
        {
            query[a].push_back({b, i});
            query[b].push_back({a, i});
        }
    }
    
    for(rl i=1; i <= n; ++ i) p[i] = i; 
    
    dfs(1, -1);
    tarjan(1);
    
    for(rl i=1; i <= m; ++ i)
        cout << res[i] << endl;
    return 0;
}

O(1) 复杂度求LCA

我们首先求出来树上每个点的 \(dfs\) 序列。

我们考虑 \(u\) 和 \(v\) 之间有什么,令 \(lca(u, v) = x, dfn_u < dfn_v\)。那么 \(x\) 到 \(v\) 路径上的一点,一定是 \(u \to v\) 路径上一点,并且这个点是 \(u \to v\) 深度最小的,这个点的父亲节点就是 \(x\)。

再考虑一种特殊情况,当\(u\) 就是 \(v\) 的祖先的时候,深度最小得点就变成了\(u\), 为了规避这种情况,我们就选择在 \([dfn_u + 1 \to dfn_v]\) 上来查询。

区间深度最小可以使用 ST表 来维护。

下面是代码,也建议背过。

inline ll get(ll a, ll b) { return dep[a] < dep[b] ? a : b; }

inline void dfs(ll u, ll fa)
{
    dfn[u] = ++ idx, st[0][idx] = fa, dep[u] = dep[fa] + 1;
    for(rl i=h[u]; ~i; i = ne[i])
    {
        ll v = e[i];
        if(v == fa) continue;
        dfs(v, u);
    }
}

inline void init()
{
    dfs(1, -1);
    for(rl i=1; (1 << i) <= n; ++ i)
        for(rl j=1; j <= n - (1 << i) + 1; ++ j)
            st[i][j] = get(st[i-1][j], st[i-1][j + (1 << i - 1)]);
}

inline ll lca(ll a, ll b)
{
    if(a == b) return a;
    a = dfn[a], b = dfn[b];
    if(a > b) swap(a, b);
    ll l = __lg(b - a); // 本人亲测用__lg(b - a),和log2(b - a) 都可以,这两个得区别可以上网搜。
    return get(st[l][a + 1], st[l][b - (1 << l) + 1]);
}

标签:浅谈,dep,ll,st,fa,关于,LCA,rl,inline
From: https://www.cnblogs.com/carp-oier/p/17744241.html

相关文章

  • 关于 GitHub 强制要求 2FA
    GitHub要求用户在今年年底之前开启双身份验证我认为这样做虽然让账号更安全,但是很浪费时间而且我是一个初中生,很多时候手机不在身边,于是就无法登录GitHub在学校就更不可能登上GitHub了这是我的一封邮件的部分内容:Bysavingyourrecoverycodes,you’llbeabletoreg......
  • 关于洛谷题解审核
    我想问一下,大家觉得题解的重点是什么?很显然是思路,代码的正确性,次要的才是格式。但是,洛谷对于题解格式的审核是不是有点过于严格了呢?比如说这段话:如果\(n\)为\(0\),那么便是无解。大家能一眼看出,后面多了空格吗?这种题解其实没什么大问题,别人看题解时根本不会在意这些......
  • 关于线段树
    动态开点当到了未建立过的新点时再建立点,一般用结构体来存储线段树。大致代码:#definelxtree[x].l#definerxtree[x].r#definemid((l+r)>>1)intcnt;structnode{ intl,r; intv;}tree[N<<5];inlinevoidpushup(intx){ tree[x].v=tree[lx].v+tree......
  • 关于智能安防及视频监控系统EasyCVR的详细介绍
    安防视频监控平台EasyCVR是一个具有强大拓展性、灵活的视频能力和轻便部署的平台。它支持多种主流标准协议,包括国标GB28181、RTSP/Onvif、RTMP等,还可以支持厂家的私有协议和SDK接入,例如海康Ehome、海大宇等设备的SDK。该平台不仅拥有传统安防视频监控的功能,还具备接入AI智能分析的......
  • 关于一类期望 dp 的公式推导
    想写但想不起来写啥......
  • 有关于Mysql的简单问题及示例(增删改查 一对一 多对多 左外连接 右外链接)
    Mysql1、请自行设计表并针对该表练习最基本的增删改查且写出示例代码建立表格class其中有属性nameidgenderinterest表格建立完成向表中插入数据插入数据完成尝试删除表中id=101的数据删除数据成功尝试修改表中id为102的数据修改成功2、请问什么是一对多?请自......
  • 关于 Failed to bind properties under 'sky.alioss.access-key-id' to java.lang.Str
    问题描述废话不多说,上截图解决方案问题出现的原因:因为自己没有按照格式去运行程序,在yml中把他们得位置向前一个单位就解决问题了......
  • 关于Async、Await的一些知识点
    在ASP.NETCore中,当一个HTTP请求到达服务器时,它会被分配给线程池中的一个线程来处理。该线程会执行相应的Controller方法。如果这个方法是一个异步方法并且使用了await关键字,那么在await的代码执行完毕之前,这个线程会被释放回线程池,可以用来处理其他的HTTP请求。当await的代码执......
  • 关于斐波那契数列 - 2 (平方的和)
    令斐波那契数列的第\(i\)项定义为\(b_i\)。再令\(f_n=\underset{i=1}{\overset{n}{\sum}}b^2_i\)结论:\(f_n=b_n\timesb_{n+1}\)首先,不难发现,该结论对于\(n=1\)和\(n=2\)一定是成立的\[f_1=1=1\times1\]\[f_2=1+1=2=1\times2\]......
  • 关于斐波那契数列 - 1
    令斐波那契数列第\(i\)个为\(F_i\)\(F_0=0,F_1=1,F_2=1\…\…\)结论:\(F_n^2=F_{n-1}F_{n+1}-(-1)^n\)不难发现,这一结论对于\(n=1\)显然是成立的接下来,运用数学归纳法若该结论对于\(n=k-1\)成立则\(F_{k-1}^2=F_{k-2}F_{k}-(-1)^{k......