最近公共祖先(LCA):倍增

https://www.luogu.org/problem/show?pid=3379
最近公共祖先方法有很多的,现在我们利用倍增表求lca
何为倍增表,简单的说是成倍增加表
bz[i][j]表示在第i位上向前推进2^j步
对于一颗有根树,bz[i][j]表示从第i为向根节点走2^j步,就是说高度增加2^j
倍增表功能强大,这里不展开(我不会…)
首先我们讲一下倍增
它的地推公示bz[i][j]=bz[bz[i][j-1]][j-1]
和st表很像的
对于一棵树,我们可以dfs求出bz[i][0]
就是i节点的父节点2^0=1;
然后

for(int j=1;n>=(1<<j);j++)
        for(int i=1;i<=n;i++)
            if(bz[i][j-1])
                bz[i][j]=bz[bz[i][j-1]][j-1];

在爆搜的同时,我们直接求出deep[i],即深度
然后每读入两个数x,y(deep[y]>=deep[x]),如果他们有高度差,先把较深的点顺着根节点向上爬,爬到两个点相同高度;
首先我们搞一个j=0;不断增加j,使deep[bz[y][j]]>deep[x]
那么deep[bz[y][j-1]]一定小于deep[x]且距离deep[x]较近
显然因为y和bz[y][j-1]都在x的下面所以他们公共最近祖先是一样的
那我们把y提升到bz[y][j-1]的位置
继续重复,直到deep[x]==deep[y];
当然咯j不断增加,有可能bz[y][j]比根节点还大,但这时deep[bz[y][j]]是0,所以不会对答案有影响;

提升到同一高度后,我们就可以同时提升xy,方法和上面一样
直到x==y当然,每个节点本身也是自己的祖先

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
struct cs{
    int to,next;
}a[2000004];
int head[600005],deep[600005],bz[600001][25];
int p,n,m,x,y,z,ll;
void inc(int x,int y){
    ll++;
    a[ll].to=y;
    a[ll].next=head[x];
    head[x]=ll;
}
void dfs(int x,int y,int z){
    bz[x][0]=y;
    deep[x]=z;
    int k=head[x];
    while(k){
        if(a[k].to!=y) dfs(a[k].to,x,z+1);
        k=a[k].next;
    }
}
int upone(int stdd,int x){//把两个点提升到相同高度 
    while(deep[x]!=stdd){
        int j=0;
        while(deep[bz[x][j]]>=stdd)j++;
        x=bz[x][j-1];
    }
    return x;
}
int happytogether(int x,int y){//两个点一起提升,只现在更新x点,从y点过来 
    if(x==y)return x;
    while(1){
        int j=0;
        if(bz[x][j]==bz[y][j])return bz[x][j];
        while(bz[x][j]!=bz[y][j])j++;
        j--; x=bz[x][j]; y=bz[y][j];
    }
}
int lca(int x,int y){
    if(deep[x]>deep[y])swap(x,y);
    y=upone(deep[x],y);
    return(happytogether(x,y));
}
int main()
{
    scanf("%d%d%d",&n,&m,&p);
    for(int i=1;i<=n-1;i++){
        scanf("%d%d",&x,&y);
        inc(x,y);
        inc(y,x);
    }
    dfs(p,-6,1);
    for(int j=1;n>=(1<<j);j++)
        for(int i=1;i<=n;i++)
            if(bz[i][j-1])
                bz[i][j]=bz[bz[i][j-1]][j-1];
    while(m--){
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }
}

在复习的时候,我利用noip集训的知识,发现了一种更好的写法;
我们对于两个点高度差值,不用去倍增其答案,而是吧这个值直接拆成二进制来处理;
在时间复杂的上并不能快多少,但是在写的时候就不用考虑倍增的边界条件,方便多了;

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
struct cs{int to,nxt;}a[N*2];
int f[N][20],dp[N];
int head[N],ll;
int n,m,x,y,S;
void dfs(int x,int y,int z){
    f[x][0]=y;dp[x]=z;
    for(int k=head[x];k;k=a[k].nxt)
        if(a[k].to!=y)dfs(a[k].to,x,z+1);
}
int lca(int x,int y){
    if(dp[x]<dp[y])swap(x,y);
    for(int j=19;j>=0;j--)
        if(dp[f[x][j]]>=dp[y])x=f[x][j];
    if(x==y)return x;
    int ans;
    for(int j=19;j>=0;j--)
        if(f[x][j]==f[y][j])ans=f[x][j];else
            x=f[x][j],y=f[y][j];
    return ans;
}
void init(int x,int y){a[++ll].to=y;a[ll].nxt=head[x];head[x]=ll;}
int main()
{
    scanf("%d%d%d",&n,&m,&S);
    for(int i=1;i<n;i++){
        scanf("%d%d",&x,&y); 
        init(x,y);init(y,x);
    }
    dfs(S,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];
    while(m--){
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 JavaScript 编写的记忆游戏(附源代码)   项目:JavaScript 记忆游戏(附源代码) 记忆检查游戏是一个使用 HTML5、CSS 和 JavaScript 开发的简单项目。这个游戏是关于测试你的短期 记忆技能。玩这个游戏 时,一系列图像会出现在一个盒子形状的区域中 。玩家必须找到两个相同的图像并单击它们以使它们消失。 如何运行游戏? 记忆游戏项目仅包含 HTML、CSS 和 JavaScript。谈到此游戏的功能,用户必须单击两个相同的图像才能使它们消失。 点击卡片或按下键盘键,通过 2 乘 2 旋转来重建鸟儿对,并发现隐藏在下面的图像! 如果翻开的牌面相同(一对),您就赢了,并且该对牌将从游戏中消失! 否则,卡片会自动翻面朝下,您需要重新尝试! 该游戏包含大量的 javascript 以确保游戏正常运行。 如何运行该项目? 要运行此游戏,您不需要任何类型的本地服务器,但需要浏览器。我们建议您使用现代浏览器,如 Google Chrome 和 Mozilla Firefox, 以获得更好、更优化的游戏体验。要玩游戏,首先,通过单击 memorygame-index.html 文件在浏览器中打开游戏。 演示: 该项目为国外大神项目,可以作为毕业设计的项目,也可以作为大作业项目,不用担心代码重复,设计重复等,如果需要对项目进行修改,需要具备一定基础知识。 注意:如果装有360等杀毒软件,可能会出现误报的情况,源码本身并无病毒,使用源码时可以关闭360,或者添加信任。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值