2019年7月训练(陆)

模板:luogo P3379 【模板】最近公共祖先(LCA)

今天讲的时候有点跑神,现在卑微地来补习(菜)

LCA指的是最近公共祖先(Least Common Ancestors)。

最简单的算法无疑是从两个点一个个往上走,出现的第一个两个点都走过的点即为两点的LCA。

但是时间很长。

所以起用倍增,倍增的作用就是将两点上升所需的复杂度减低

大致流程为:将deep不同的两个点跳到同一层,再跳到deep[lca-1]的那层,再向上跳一层就是lca了。

加速跳的方法就是每次向上跳的层数为2的i次方层,就是把层数转化成2进制的数,这样时间复杂度就变为log2(n)了。

之后要两个数组f[i][j](从i向上2^j层后到达的点)和deep[i](这棵树中i点的深度)。

deep[i]用一个dfs求得。

f[i][j]用了递推,f[i][j]=f[f[i][j-1]][j-1]。初始化f[i][0]也可以在遍历整棵树的时候求得。

然后,两点再同时向上逼近。i从最高位开始枚举,假设两点分别为x,y,那么能向上跳的判断式为:

if (f[x][i]!=f[y][i])
{
       x=f[x][i];
       y=f[y][i];
}

就是如果两点向上跳了2^i层以后不到同一个点就接着往上跳。为什么这样?因为如果往上跳了2^i层,即使到了同一个点,它不一定是两点的LCA。

这样做,最终就会到达LCA的下面一层。随后,我们再将两点向上跳一层。LCA求得。

然后这个让我无比摸不着头脑的问题出现了:

为什么最终会到达LCA的下面一层?

我们假设从a,b点开始,往上跳2^j层,跳到同一点。不跳。往上跳2^(j-1)层,不跳到同一点,往上跳,分别到了A‘,B‘。显然,这种情况是一定会存在的。那么,从A‘,B’再往上跳到原来那个决定不跳的点,显然要跳2^(j-1)层。那么,那个点有可能是LCA,也有可能不是,对吧?所以,从A‘,B‘往上跳到LCA所需的层数,是≤2^(j-1)的。换句话来说,A‘,B‘到X的层数变成了一个j-2位的二进制数(可能会有前导零,也就是还可能会跳到点数相同的地方)。而此时,刚好枚举到j-2位。那么,前导零不减,再这么减下去,你发现,这个层数差最终会变成0,而你最终也会到达第X层。

大概就是你的叔伯(爸爸的兄弟)不是你的祖先,这里找的祖先必须是直系的。

为什么与X层数差最终会变成0?

首先我们证明,前导零不会被减去。假设与X层的层数差为x‘,而你正准备往上跳y层。由于LCA的层数是X+1,而LCA往上的点它都不会跳,对吧?(反而,如果LCA往下的点,也就是层数<=X,也就是y<x‘+1,它都会往上跳)所以如果y>=x‘+1,那么就绝对不会往上跳。

显然,当x‘的该位为0,且属于前导零,那么只需证明x‘+1<=y。而这个非常易证(假设y为10000,而x‘满足条件的最大值为01111)。所以保证,前导零是不会减去的。

接着我们证明,一旦枚举到了x‘的第一个为1的位数,这个1绝对会被减去。按照同样的方法,假设y为10000,而x‘满足条件的最小值为10000,所以y<x‘+1.

两点合在一起,前导零不会减去,枚举到一个1位就减去,最终这个层数差就会变成0.证毕。

代码:

#include<cstdio>
#include<cstring>
#include<stack>
#include<algorithm>
#include<cmath>
#define  maxn 500010
#include<queue>
using namespace std;

int f[maxn][21],head[maxn],deep[maxn],cnt,n,m,rt;
struct edge
{
    int v,next;
}e[maxn<<1];

void add(int u,int v)
{
    e[++cnt].v=v;
    e[cnt].next=head[u];
    head[u]=cnt;
}

void dfs(int x,int fa)
{
    f[x][0]=fa;
    deep[x]=deep[fa]+1;
    for(int i=head[x];i;i=e[i].next)
    {
        int v=e[i].v;
        if(v==fa) continue;
        dfs(v,x);
    }
}

void Init()
{
    for(int j=1;(1<<j)<=n;++j)
    {
        for(int i=1;i<=n;++i)
        {
            if(deep[i]>=(1<<j))
            {
                f[i][j]=f[f[i][j-1]][j-1];
            }
        }
    }
}

int query(int x,int y)
{
    if(deep[x]<deep[y]) swap(x,y);
    int d=deep[x]-deep[y];
    for(int j=20;j>=0;--j) if(d&(1<<j)) x=f[x][j];
    if(x==y) return x;
    for(int j=20;j>=0;--j)
    {
        if(f[x][j]!=f[y][j])
        {
            x=f[x][j];
            y=f[y][j];
        }
    }
    return f[x][0];
}

int main()
{
    int u,v;
    scanf("%d%d%d",&n,&m,&rt);

    for(int i=1;i<n;++i)
    {
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
    }

    dfs(rt,0);
    Init();
    while(m--)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%d\n",query(x,y));
    }
    return 0;
}

2019-07-3122:54:32

原文地址:https://www.cnblogs.com/plzplz/p/11279752.html

时间: 2024-11-09 18:19:36

2019年7月训练(陆)的相关文章

2019年1月训练记录(更新ing)

前言 时光飞逝,转眼间,便到了\(2019\)年. 这一年里,还是要继续努力吧,好好学习一些新的算法. 当然,还是要好好准备一下期末考试啦... ... \(Jan\ 1st\) 原文地址:https://www.cnblogs.com/chenxiaoran666/p/2019Jan.html

2019年7月训练(壹)

2019-07-25 luogu P3627 [APIO2009]抢掠计划 卡了三个小时,看了题解才作出来的(菜) 前驱知识: 壹~邻接表存储/遍历 贰~SPFA跑最长路(<改>就行了) 叄~Tarjan缩点 壹.邻接表储存 两个,add存无边权,未缩点:build有边权,已缩点. void add(int u,int v) { cnt++; e[cnt].to=v; e[cnt].next=head[u]; head[u]=cnt; } void build(int u,int v,int

2019年8月训练(壹)二分,三分

二分查找 P1024 一元三次方程求解 题目给出范围[-100,100],同时两根绝对值之差<=1,保证了每一个大小为1的区间里至多有1个解,也就是说当区间的两个端点的函数值异号时区间内一定有一个解,同号时一定没有解. 也就可以二分去做查找. AC码: #include<cstdio> #include<cstring> #include<algorithm> using namespace std; double a,b,c,d; double f(double

2019年9月训练(壹)数位DP (HDU 2089)

开学之后完全没时间写博客.... HDU 2089 不要62(vjudge) 数位DP 思路: 题目给出区间[n,m] ,找出不含4或62的数的个数 用一个简单的差分:先求0~m+1的个数,再减去0~n的个数. 但问题依旧不简单,再次简化为求0~i位数中不含4或62的数的个数. i=1 //0~9中 i=2 //0~99中 i=3 //0~999中 ...... dp[i][0] //0~i位数中的吉利数 dp[i][1] //0~i位数中以2打头的吉利数 dp[i][2] //0~i位数中的非

周记 - 2019年11月03日

2019年11月05日 2019年徐州区域赛结束了.封榜前3题铜牌前部,封榜后最后27分钟罚了4次通过E题.虽然4题罚时爆炸,不过万幸得了银牌后部.目前看应该还会再参加一年的,这个博客会不断更新记录最后一年参加比赛的学习进度(以及最后两年本科的其他事情),今年的目标是做一个真正的全能选手,首先希望在寒假结束前把Codeforces打到橙色(2100+),证明自己思维还行吧. 既然想做全能选手,每个专题都要学到省选级别吧,学习的分类就参照OI-Wiki的分类,题单的话,模板题从洛谷找,思维题从Co

蔡康永的说话之道——2019年12月15日

.bodyContainer { font-family: Arial, Helvetica, sans-serif; text-align: center; padding-left: 32px; padding-right: 32px; } .notebookFor { font-size: 18px; font-weight: 700; text-align: center; color: rgb(119, 119, 119); margin: 24px 0px 0px; padding:

西安活动 | 2019年1月13号 &quot;拥抱开源, 又见.NET&quot; 线下交流活动报名进行中

随着.NET Core的发布和开源,.NET又重新回到人们的视野..NET Core的下个3.0即将release,加入非常多的新功能,越来越拥抱变化,DevOps和Microservice的最佳实践已经在.NET Core落地,比如 Ocelot网关.Grpc+Consul 服务注册发现.Apworks CQRS实现.Xigadee 微服务工具库.脚手架. 西安.NET社区组织发起了此次“拥抱变化, 又见.NET”线下交流活动,邀请了三位资深.NET开发者作为分享讲师,他们将从架构.原理.语言

2019年2月26日【整理物品,下载收集考研资料,明天正式开始复习】

2019年1月26日星期六 一:一句话木马重学习 1.网站安全狗网马查杀 http://download.safedog.cn/download/software/safedogwzApache.exe 2.D盾 Web 查杀 http://www.d99net.net/down/WebShellKill_V2.0.9.zip 3 深信服WebShellKillerTool http://edr.sangfor.com.cn/tool/WebShellKillerTool.zip 4 BugSc

java 手机号正则表达式 2019年1月

  import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; /**  * @author wucai  *三大运营商号码均可验证(不含卫星通信1349)  */ public class mobile {     /*  <br> 2019年1月16日已知     中国电信号段         133,149,153,173,174,