Fzu 2207 以撒的结合【思维+Dfs序】好题!好题!

本文介绍了一种算法解决方案,用于确定游戏《以撒的结合》中从一个房间到另一个房间路径上的特定位置。通过预处理最短路径并利用DFS序进行优化,实现了高效的查询处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 Problem 2207 以撒的结合

Accept: 68    Submit: 219
Time Limit: 1000 mSec    Memory Limit : 32768 KB

 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<n<=1000,0<m<=n*n),分别表示房间个数和询问次数。

接下来n-1行,每行两个整数u v(0<u、v<=n,且u≠v),表示地图上房间u和房间v有一条路径。

最后是m行,每行三个整数u v k,表示询问房间u到房间v的路径上的第k个房间。

输入数据保证合法,即k不超过u、v的最短距离。

 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

 Source

FOJ有奖月赛-2015年11月

思路:


1、观察到N的范围不大,我们肯定可以O(n^2) 处理出来dist【i】【j】,表示从i走到j的最短距离。

那么如果存在一个询问u,v,k;

那么如果存在一个点tmp,使得dist【u】【tmp】+dist【tmp】【v】==dist【u】【v】&&dist【u】【tmp】==k,那么这个点tmp就是结果了。

然而对于每一个查询都这样去判断的话,时间复杂度会达到O(n^2+n*m);这里m很大,可以近似看做O(n^3);是肯定要TLE掉的。


2、那么我们考虑优化。

其实我们知道,这样的tmp点是唯一的。

而且如果我们以点u作为树的根的话,u和tmp都将会是点v的祖先。

我们可以处理Dfs序,来O(1)询问一个点是否是另一个点的祖先。


那么我们不妨离线去做。

那么我们设定q【i】【j】表示从点i,到某些点,距离为j的询问。

那么vector<>q【i】【j】需要存入两个信息,一个是查询的编号,另一个是查询的点。


接下来我们O(n)枚举一个点作为根,然后遍历整颗树,得到一个Dfs序,过程得到dist【i】【j】;

然后我们O(n)去枚举一个点作为结果点,那么对应dist【i】【j】就是两点间距离。

那么对于当前枚举出来的结果点,我们解决相关q【i】【dist[i][j]】中存入的询问。

设定点v为查询,即v=q【i】【dist[i][j]】.v;

如果当前点j,有:L【v】>=L【i】&&L【v】<=R【j】;那么此时就是说明,点j是点v的祖先。

那么成立:点j是从i到v的路径上的第dist【i】【j】个点。


整体查询时间复杂度O(n^2+m);


Ac代码:

#include<stdio.h>
#include<string.h>
#include<vector>
using namespace std;
struct node
{
    int v,pos;
};
int ans[1050*1050];
int L[1050];
int R[1050];
int dist[1050][1050];
vector<int >mp[1050];
vector<node >q[1050][1050];
int n,m;
int tim;
void Dfs(int root,int u,int from,int depth)
{
    L[u]=++tim;
    dist[root][u]=depth;
    for(int i=0;i<mp[u].size();i++)
    {
        int v=mp[u][i];
        if(v==from)continue;
        Dfs(root,v,u,depth+1);
    }
    R[u]=tim;
}
int main()
{
    int t;
    int kase=0;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)mp[i].clear();
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                q[i][j].clear();
            }
        }
        for(int i=0;i<n-1;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            mp[x].push_back(y);
            mp[y].push_back(x);
        }
        for(int i=0;i<m;i++)
        {
            int u,v,len;
            scanf("%d%d%d",&u,&v,&len);
            node now;
            now.v=v;
            now.pos=i;
            q[u][len].push_back(now);
        }
        for(int i=1;i<=n;i++)
        {
            tim=0;
            Dfs(i,i,-1,1);
            for(int j=1;j<=n;j++)
            {
                for(int k=0;k<q[i][dist[i][j]].size();k++)
                {
                    node now=q[i][dist[i][j]][k];
                    int v=now.v;
                    //j是v的祖先
                    if(L[v]>=L[j]&&L[v]<=R[j])
                    {
                        ans[now.pos]=j;
                    }
                }
            }
        }
        printf("Case #%d:\n",++kase);
        for(int i=0;i<m;i++)
        {
            printf("%d\n",ans[i]);
        }
    }
}














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值