树——仓鼠找 sugar

文章讲述了如何利用最近公共祖先(LCA)算法解决小仓鼠和基友在地下洞穴中寻找相遇点的问题,通过比较路径上的节点深度来判断是否有公共点。算法涉及树的遍历和深度计算,以确定两个节点间的最近公共祖先以及路径是否相交。
摘要由CSDN通过智能技术生成

仓鼠找 sugar

题目来源:https://www.luogu.com.cn/problem/P3398

题目描述

小仓鼠的和他的基(mei)友(zi)sugar 住在地下洞穴中,每个节点的编号为 1 ∼ n 1\sim n 1n。地下洞穴是一个树形结构。这一天小仓鼠打算从从他的卧室( a a a)到餐厅( b b b),而他的基友同时要从他的卧室( c c c)到图书馆( d d d)。他们都会走最短路径。现在小仓鼠希望知道,有没有可能在某个地方,可以碰到他的基友?

小仓鼠那么弱,还要天天被 zzq 大爷虐,请你快来救救他吧!

输入格式

第一行两个正整数 n n n q q q,表示这棵树节点的个数和询问的个数。

接下来 n − 1 n-1 n1 行,每行两个正整数 u u u v v v,表示节点 u u u 到节点 v v v 之间有一条边。

接下来 q q q 行,每行四个正整数 a a a b b b c c c d d d,表示节点编号,也就是一次询问,其意义如上。

输出格式

对于每个询问,如果有公共点,输出大写字母 Y;否则输出N

样例 #1

样例输入 #1

5 5
2 5
4 2
1 3
1 4
5 1 5 1
2 2 1 4
4 1 3 4
3 1 1 5
3 5 1 4

样例输出 #1

Y
N
Y
Y
Y

提示

本题时限 1s,内存限制 128M,因新评测机速度较为接近 NOIP 评测机速度,请注意常数问题带来的影响。

20 % 20\% 20% 的数据 n , q ≤ 200 n, q\le200 n,q200

40 % 40\% 40% 的数据 n , q ≤ 2 × 1 0 3 n, q\le 2\times10^3 n,q2×103

70 % 70\% 70% 的数据 n , q ≤ 5 × 1 0 4 n, q\le 5\times10^4 n,q5×104

100 % 100\% 100% 的数据 1 ≤ n , q ≤ 1 0 5 1\le n, q\le10^5 1n,q105

总结

最近公共祖先

最近公共最先又称lca,它可以解决以下问题:

  • 在树中可以更快的找到公共祖先,可以通过两种方式实现:
    | 1. 通过检测二者的depth来判断是否要往上跳,采用二进制的形式进行扩增法来跳,这种方法需要用bfs来初始化depth数组
    | 2 .采用targin算法,这种算法是通过dfs的方式先将树标记成3种状态,第一种是未遍历,第二种是正在遍历,第三种是已遍历并回溯

  • 次小生成树,通过kurscal算法(与上述的bfs算法写法类似),就是要用结构体(kurscal算法都是这样写的)来存储,关键是有判断是不是树节点,因为最后要使sum+w-wi最小,sum是权值。所以lca可以快速计算出a,b之间的最大边权与次大边权。

  • 总之lca可以利用倍增的方式快速的计算出某个节点与公共祖先之间的距离,比如da+db-2dp

  • lca可以用来算树中节点距离

  • 两条路求是否能碰面,就像这道题,我们得知道一个结论,询问树上
    a到b,c到d的两条路径是否相交我们容易发现,如果相交,记x=lca(a,b)y=lca(c,d),则必有x在c~ d路径上或y在a~ b路径上,——引用来源

注意,LCA这样的题一般题目都有都以最短路算……然后求什么什么碰面或者某个人走到某个地方是否喜欢某个东西

代码

//感觉有点lca,但一般的lca题目只要求两点的最近公共祖先,但这道题有个结论
//如果a=lca(x,y),b=lca(z,k),如果dist(a,x)+dist(x,b)==dist[a,b]

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>

using namespace std;

const int N = 1e5+10,M = 2*N;

int f[N][17];
int n,m;
int depth[N];
int e[M],ne[M],h[N],idx;

void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void bfs(int u){
    memset(depth,0x3f,sizeof depth);
    queue<int>q;
    depth[0]=0;
    depth[u]=1;
    q.push(u);
    
    while(q.size()){
        int t=q.front();
        q.pop();
        
        for(int i=h[t];~i;i=ne[i]){
            int j=e[i];
            
            if(depth[j]>depth[t]+1){
                depth[j]=depth[t]+1;
                q.push(j);
                
                f[j][0]=t;
                
                for(int k=1;k<=16;k++){
                    f[j][k]=f[f[j][k-1]][k-1];
                }
            }
            
            
        }
    }
}

int lca(int x,int y){
    if(depth[x]<depth[y])swap(x,y);
    
    for(int k=16;k>=0;k--){
        if(depth[f[x][k]]>=depth[y]){
            x=f[x][k];
        }
    }
    
    if(x==y)return x;
    
    for(int k=16;k>=0;k--){
        if(f[x][k]!=f[y][k]){
            x=f[x][k];
            y=f[y][k];
        }
    }
    
    return f[x][0];
}

int dist(int a,int b){
    int c=lca(a,b);
    
    return abs(depth[c]-depth[a])+abs(depth[c]-depth[b]);
}

int main(){
    cin>>n>>m;
    
    memset(h,-1,sizeof h);
    
    for(int i=0;i<n-1;i++){
        int a,b;
        cin>>a>>b;
        add(a,b),add(b,a);
    }
    
    bfs(1);
    
    while(m--){
        int x1,y1,x2,y2;
        cin>>x1>>y1>>x2>>y2;
        int x=lca(x1,y1),y=lca(x2,y2);
        
        if(dist(x1,y)+dist(y1,y)==dist(x1,y1)||dist(x2,x)+dist(y2,x)==dist(x2,y2)){
            puts("Y");
        }else{
            puts("N");
        }
    }
    
    return 0;
}

细节:不要在那个for循环的时候把k>=0写成k了

LCA模板

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>

using namespace std;

const int N = 5e5+10,M = 2*N;

int e[M],ne[M],h[N],idx;
int f[N][17];//f[i][j] 从i开始向上走2^j步所能走到的节点
int n,m,root;
int depth[N];

void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void bfs(int root){
    memset(depth,0x3f,sizeof depth);
    depth[0]=0;
    depth[root]=1;
    queue<int> q;
    
    q.push(root);
    
    while(q.size()){
        int t=q.front();
        q.pop();
        
        for(int i=h[t];~i;i=ne[i]){
            int j=e[i];
            
            if(depth[j]>depth[t]+1){
                depth[j]=depth[t]+1;
                q.push(j);
                
                f[j][0]=t;//j跳0步就到了t
                
                for(int k=1;k<=15;k++){
                    f[j][k]=f[f[j][k-1]][k-1];//倍增的思想
                }
            }
        }
        
    }
}

int lca(int x,int y){
    if(depth[x]<depth[y])swap(x,y);
    
    for(int k=15;k>=0;k--){
        if(depth[f[x][k]]>=depth[y]){
            //x深度比y深
            x=f[x][k];
        }
    }
    
    if(x==y)return x;
    
    for(int k=15;k>=0;k--){
        if(f[x][k]!=f[y][k]){
            x=f[x][k];
            y=f[y][k];
        }
    }
    
    return f[x][0];
}

int main(){
    cin>>n>>m>>root;
    memset(h,-1,sizeof h);
    for(int i=1;i<=n-1;i++){
        int a,b;
        cin>>a>>b;
        add(a,b),add(b,a);
    }
    
    bfs(root);
    
    while(m--){
        int x,y;
        cin>>x>>y;
        int p=lca(x,y);
        cout<<p<<endl;
    }
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

green qwq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值