几个基础树上问题

在这里插入图片描述

一.喝牛奶

在这里插入图片描述
       &nbspl;考虑维护一个并查集,将互相连通且奶牛种类相同的农场放进同一个连通块中。只有两个点在同一个连通块中且该连通块中的牛奶种类与客人想喝的牛奶种类不同时才输出0.(若两点不在一个连通块中,可以得到两种牛奶,自然满足要求;若客人要求的牛奶种类与连通块中牛奶种类相同也满足要求)

#include<bits/stdc++.h>
using namespace std;
const int K = 100010;
int p[K];
char str[K];
int find(int x)
{
   
    if(p[x]!=x) p[x] = find(p[x]);
    else return p[x];
}
int main()
{
   
    int N,M;
    cin>>N>>M;
    for(int i = 1;i<=N;i++)  p[i] = i;
    for(int i = 1;i<=N;i++) cin>>str[i];
    for(int i = 1;i<=N-1;i++)
    {
   
        int X,Y;
        cin>>X>>Y;
        if(str[X]==str[Y])  p[find(X)] = find(Y);
    }
    while(M--)
    {
   
        int A,B;
        char C;
        cin>>A>>B>>C;
        if(find(A)==find(B)&&str[A]!=C) cout<<"0";
        else    cout<<"1";
    }
    return 0;
}

二.LCA

        首先我们介绍一下什么是LCA。LCA全称为“lowest commen ancestor”,即最近公共祖先。最近公共祖先的定义如图。
在这里插入图片描述
        一个节点到根节点路径上所有的点(包括它自身)被称为该点的祖先。最近公共祖先就是两个点的公共祖先中深度最大的那个(若两点相同,它们的最近公共祖先就是它们自身)。如图中3号点与5号点的最近公共祖先就是3本身,3号点和1号点的最近公共祖先是根节点。为了方便,以下我们约定根节点深度为1,0号节点(根节点上层的所有不存在的节点)深度为0。那么该如何寻找最近 公共祖先呢?

二.1.朴素LCA算法

        其中一个点向根节点走,每走到一个点就标记一个(自身也要标记)。然后另外一个点也向根节点出发,当它第一次走到标记点,这个标记点就是最近公共祖先。

二.2.倍增LCA算法(log时间复杂度)

        分为两步。(1)较深的点跳到较浅的点的同一层。
(2)两个点一起向上跳到最近公共祖先的下一层。需要注意的是,这两次跳跃的步长都是2的整数次幂。为此我们要预处理两个数组,第一个是depth,记录每个节点的深度信息;第二个是fa,fa[i][j]表示从编号为i的点向上跳2^j步到达的点的编号。跳跃时,我们从2的高次幂步长开始跳。比如两个点之间相差了11曾层,我们就先跳8层,再跳2层,最后跳一层;两个点在同一层后,如果离最近公共祖先是11层,我们也先跳8层,再跳2层。接下来让我们看看具体的代码实现吧!

#include<bits/stdc++.h>
using namespace std;
const int N = 40010;
//因为是无向树,两个点之间要存两条边
const int M = N*2;
int n,m;
int h[N],e[M],ne[M],idx;
//2^15 = 32768;
int depth[N],fa[N][16];
//队列
int q[N];
void add(int x,int y)
{
   
    e[idx] = y;
    //这里写ne[x]就错了!因为不是插入到两个点中间!是新开一个点!比如5是一个父节点,3是一个已经有的子节点,如果这时候再插入1写成ne[idx] = ne[x],会形成5->1->3!
    ne[idx] = h[x];
    h[x] = idx++;
}
void bfs(int root)
{
   
    memset(depth,0x3f,sizeof depth);
    depth[0] = 0;
    depth[root] = 1;
    //定义一个队列
    int hh = 0,tt = 0;
    q[0] = root;
    //不能写小于!否则无法进入循环!
    while(hh<=tt)
    {
   
        //取出队头
        int t = q[hh++];
        //此时的ne指向的是同一层的节点!
        for(int i = h[t];i>=0;i = ne[i])
        {
   
            int j = e[i];
            //由于初始时设定depth为正无穷,大于说明没被遍历到
            if(depth[j]>depth[t]+1)
            {
   
                depth[j] = depth[t]+1;
                q[++tt] = j;
                fa[j][0] = t;
                //不能写成k<=16!
                for(int k = 1;k<=15;k++)
                    fa[j][k] = fa[fa[j][k-1]][k-1];
                    
            }
        }
    }
}
int lca(int a,int b)
{
   
    //我们假设a在b的下面。如果在上面,就交换
    if(depth[a]<depth[b])   swap(a,b);
    for(int k = 15;k>=0;k--)
    //哨兵节点的好处在这里体现出来。如果a不小心跳到哨兵节点了,哨兵节点的深度是0,,不会比depth[b]大,a也就不会上跳。如果甚至连哨兵节点也跳出了,那么本来的depth值默认就是0,自然也不会往上跳
        if(depth[fa[a][k]]>=depth[b])   a = fa[a][k];
    if(a==b)    return a;
    for(int k = 15;k>=0;k--)
    {
   
        //如果跳出去,两个都是0,不会不相等,不执行条件后的语句
        //fa[a][k]!=fa[b][k]说明还没到公共祖先
        if(fa[a][k]!=fa[b][k])
        {
   
            //一起跳
            a = fa[a][k];
            b = fa[b
  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值