FZU2207 以撒的结合(最近公共祖先lca,在线倍增)

本文介绍了一个基于《以撒的结合》游戏背景的路径寻址问题及其解决方案。通过深度优先搜索预处理节点深度及最近公共祖先,实现快速定位玩家与补血红心之间的指定位置。

Problem Description

小茗同学最近在认真地准备比赛,所以经常玩以撒的结合。

《以撒的结合》是一款由Edmund McMillen,Florian Himsl 开发,并由Edmund
McMillen最早于2011年09月29日发行的一款2D平面角色扮演、动作冒险类的独立游戏。游戏的角色将在有着能够提升能力的道具与特殊技能的半RPG世界中闯荡。

——来自百度百科


小茗同学在打BOSS前,费掉了很多HP。在地图的一些房间里有补充HP的红心,然而小茗同学受到了看不见地图的诅咒。凭借不知道哪里来的记忆,小茗同学记得某个有红心的房间在房间A与房间B的路上的第K个房间里。为了简化问题,我们把地图看成一棵树。小茗同学想知道A到B的第K个房间号为多少,由于小茗同学很累,所以现在这个任务交给你了。

Input

第一行是一个整数T(T<=10),表示有T组测试数据。

每组数据的第一行为两个整数n m(0

Output

对于每组数据,首先第一行先输出“Case #x:“ ,其中x是从1开始,表示数据组号,接下来m行,每行输出相应的房间号。

Sample Input

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

Sample Output

Case #1:
3
3
5

思路

首先dfs一遍处理一下深度,然后对于每一组询问,找出这两个的最近公共祖先,然后从u->v判断是在公共祖先的左边还是右边,然后从左边或者右边向上找父亲节点就可以了

代码

#include <cstdio>
#include <cstring>
#include <cctype>
#include <stdlib.h>
#include <string>
#include <map>
#include <iostream>
#include <stack>
#include <cmath>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
#define inf 1000000
#define mem(a,b) memset(a,b,sizeof(a))
const int N=2000+7;

int n,m,s;
int tot;
int first[N],d[N],p[N][21];

struct edge
{
    int v,next;
} e[2*N];

void add_edge(int u,int v)
{
    e[tot].v=v;
    e[tot].next=first[u];
    first[u]=tot++;
}

void dfs(int u,int fa)
{
    d[u]=d[fa]+1;
    p[u][0]=fa;
    for(int i=1; (1<<i)<=d[u]; i++)
        p[u][i]=p[p[u][i-1]][i-1];
    for(int i=first[u]; ~i; i=e[i].next)
    {
        int v=e[i].v;
        if(v!=fa)
            dfs(v,u);
    }
}

int lca(int a,int b)
{
    if(d[a]>d[b]) swap(a,b);//保证a在b点的上方
    for(int i=20; i>=0; i--)
        if(d[a]<=d[b]-(1<<i))
            b=p[b][i];  //把b移到和a同一个深度
    if(a==b) return a;
    for(int i=20; i>=0; i--)
    {
        if(p[a][i]==p[b][i])
            continue;
        else
            a=p[a][i],b=p[b][i];//一起向上跳跃
    }
    return p[a][0];
}

void init()
{
    tot=0;
    mem(first,-1);
    mem(d,0);
    mem(p,0);
}
int calc(int ans,int x)
{
    for(int i=0; (1<<i)<=x; i++)
    {
        if(x&(1<<i))
            ans=p[ans][i];
    }
    return ans;
}
int main()
{
    //freopen("in.txt","r",stdin);
    int t,u,v,k,q=1;
    scanf("%d",&t);
    while(t--)
    {
        init();
        scanf("%d%d",&n,&m);
        for(int i=1; i<n; i++)
        {
            scanf("%d%d",&u,&v);
            add_edge(u,v);
            add_edge(v,u);
        }
        printf("Case #%d:\n",q++);
        dfs(1,0);
        for(int i=1; i<=m; i++)
        {
            scanf("%d%d%d",&u,&v,&k);
            int root=lca(u,v);
            int z=d[u]-d[root]+1;
            int y=d[v]-d[root]+1;
            if(k==z)
            {
                printf("%d\n",root);
            }
            else if(k<z)
            {
                int ans=u;//跳k-1步
                printf("%d\n",calc(ans,k-1));
            }
            else if(k>z)
            {
                k=k-z+1;
                k=y-k+1;//跳k步
                int ans=v;
                printf("%d\n",calc(ans,k-1));
            }
        }
    }
    return 0;
}

向上跳的时候暴力:

#include <cstdio>
#include <cstring>
#include <cctype>
#include <stdlib.h>
#include <string>
#include <map>
#include <iostream>
#include <stack>
#include <cmath>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
#define inf 1000000
#define mem(a,b) memset(a,b,sizeof(a))
const int N=1000+7;

int n,m,s;
int tot;
int first[N],d[N],p[N][21];

struct edge
{
    int v,next;
} e[2*N];

void add_edge(int u,int v)
{
    e[tot].v=v;
    e[tot].next=first[u];
    first[u]=tot++;
}

void dfs(int u,int fa)
{
    d[u]=d[fa]+1;
    p[u][0]=fa;
    for(int i=1; (1<<i)<=d[u]; i++)
        p[u][i]=p[p[u][i-1]][i-1];
    for(int i=first[u]; ~i; i=e[i].next)
    {
        int v=e[i].v;
        if(v!=fa)
            dfs(v,u);
    }
}

int lca(int a,int b)
{
    if(d[a]>d[b]) swap(a,b);//保证a在b点的上方
    for(int i=20; i>=0; i--)
        if(d[a]<=d[b]-(1<<i))
            b=p[b][i];  //把b移到和a同一个深度
    if(a==b) return a;
    for(int i=20; i>=0; i--)
    {
        if(p[a][i]==p[b][i])
            continue;
        else
            a=p[a][i],b=p[b][i];//一起向上跳跃
    }
    return p[a][0];
}


void init()
{
    tot=0;
    mem(first,-1);
    mem(d,0);
    mem(p,0);
}
int main()
{
    //freopen("in.txt","r",stdin);
    int t,u,v,k,q=1;
    scanf("%d",&t);
    while(t--)
    {
        init();
        scanf("%d%d",&n,&m);
        for(int i=1; i<n; i++)
        {
            scanf("%d%d",&u,&v);
            add_edge(u,v);
            add_edge(v,u);
        }
        printf("Case #%d:\n",q++);
        dfs(1,0);
        for(int i=1; i<=m; i++)
        {
            scanf("%d%d%d",&u,&v,&k);
            int root=lca(u,v);
            int z=d[u]-d[root]+1;
            int y=d[v]-d[root]+1;
            if(k==z)
            {
                printf("%d\n",root);
            }
            else if(k<z)
            {
                int ans=u;
                for(int i=1; i<k; i++)
                {
                    ans=p[ans][0];
                }
                printf("%d\n",ans);
            }
            else if(k>z)
            {
                k-=z;
                k=y-k;
                int ans=v;
                for(int i=1; i<k; i++)
                {
                    ans=p[ans][0];
                }
                printf("%d\n",ans);
            }
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值