google kick start round C 2019 Wiggle Walk

博主完成毕设后开始参加Google的Kick Start。文中介绍了Kick Start中一道迷宫题,给定迷宫和指令,求最终到达的格子。先分析暴力解法的复杂度问题,后提出用并查集解决,维护4个方向的并查集,还给出了具体代码实现。

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

啦啦啦,我来更新啦。最近把毕设做完了,然后开始做google的kick start了。虽然暂时不找工作,但是打了kick start可以方便参加google的其他活动(比如冬令营等等)。而作为一只算法的菜鸡,也要开始慢慢飞啦!

这道题是本轮kick start里面最简单的一道题,也是可以直接想出来的。

【题意】一个R*C的迷宫,给定你起点。给定一段指令,指令有四种,N,S,W,E,分别代表着向北走(x坐标-1),向南走(x坐标+1),向西走(y坐标-1),向东走(y坐标+1)。如果走到了之前走过的格子,就继续往当前指令的方向走,直到走到一个之前没有访问过的格子,(注:所有的指令都不会指引你走出迷宫的边界)。问,经过这么一段指令后,你能到达哪个格子。

【输入】T代表测试用例的个数,每个测试用例的第一行为N,R,C,S_R, S_C,分别代表指令的个数,迷宫的行数,迷宫的列数,起点的横坐标,起点的纵坐标。第二行为一行指令,仅由上述4种指令构成。0<R,C<=5e4, N<5e4

【小用例】

【input】

3
5 3 6 2 3
EEWNS
4 3 3 1 1
SESE
11 5 8 3 4
NEESSWWNESE

【output】

Case #1: 3 2
Case #2: 3 3
Case #3: 3 7

【思路】

首先,考虑暴力的解法,每次要到达一个点时,先看看之前有没有访问过,如果访问过,继续往当前指令方向走,一直走到没有访问过的点,然后更新改点的vis。完美~但是,这有两个问题,第一个空间复杂度,50000*50000的位置,没法用数组存;第二个,时间复杂度,一个个的往前找,复杂度是5e4*5e4(O(NR)),反正差不多是这个量级吧。

先解决第一个,这个很好解决,用map就可以了,每次查询的复杂度是O(N)。第二个比较难搞,首先想的是,既然我不一个个的找,那我就对每个点存他能到达的最东、南、西、北方向的上的点就好了。这个在查询时候的复杂度为1,但是更新的时候,是不是要一个个的更新?同时,要对哪些点进行更新?是不是要把之前的所有点都遍历才能找到更新的点?

如果是这个思路的话,可以看出,更新是一个大问题。通过别人提醒,我才发现,可以用并查集解决我的问题。首先,并查集是什么,

https://blog.youkuaiyun.com/qq_41593380/article/details/81146850

这篇文章中讲的很清楚,说白了,就是快速解决检查两点之间是否连通的一个算法,并且,还可以把两个互不连通的集合进行连接。在我们的场景中,如果我们有了并查集,可以通过寻找爸爸的方式,找到各个方向上的爸爸,这样不就快速知道我能到达的连通的最远点了吗?

具体来说,维护4个方向上的并查集,每个并查集上的老大都是该连通块的一个边缘点(比如,往南方向上的老大,就是当前连通块的最南端的点)。注意,这个是一个和普通并查集不同得地方,普通并查集没有级别的概念,也就是说,随便推举出一个点做老大就可以了,但是在我们的场景中,我们要知道最南端的点是哪个点,所以,我们就需要有一个方向上的维护:

void s_join(pair<int, int>son1, pair<int,int>son2) //x大的当老大
{
    son1 = s_find(son1);
    son2 = s_find(son2);
    if (son1 != son2)
    {
        if (son1.first > son2.first)
            south[son2] = son1;
        else
            south[son1] = son2;
    }
}

比如,在对朝南方的点进行join时,我们要判断两个点,哪个点的x大,哪个就是老大。这样,在我们find的时候,就可以找到南方的老大啦

pair<int, int> s_find(pair<int, int> root)
{
    if (root != south[root]) south[root] = s_find(south[root]);
    return south[root];
}

注意,因为这是一个退化的并查集(只有一个分支,所以要用路径压缩的方法,如上图,也就是尽量每个点的爸爸都更新为最远点)

还有一个注意点就是在对一个当前到达的点进行更新的时候,要进行东南西北四个方向的更新。这一步很重要,写错容易wa。首先,我们先设置,这个点的东南西北四个方向的爸爸都是自己。cur为当前点:

north[cur] = cur;
south[cur] = cur;
west[cur] = cur;
east[cur] = cur;

然后,我们进行连接。以南方向为例,cur点的下一个南方向上的点为next = make_pair(cur.first + 1, cur.second);

如果next已经访问过,我们就把这两个点的南北方向都要更新,也就是说,next的北边要更新为cur,cur的南边要更新为next(注意,一定要南北都更新,如果不对这两个点进行两个方向的更新,就会wa)。

在查询的时候,就简单了,只需要当前点的指令方向的最边缘的点是哪个一个,先转移到改边缘点,然后再往指令的方向走一步,到达next点,然后,对next点进行东南西北四个方向的更新就好了。

【Accept代码】

 

// ConsoleApplication3.cpp : 定义控制台应用程序的入口点。
//
#include<iostream>
#include<string>
#include<algorithm>
#include<map>
using namespace std;
const int maxn = 5e4 + 10;
map<pair<int, int>, pair<int, int>>south;
map<pair<int, int>, pair<int, int>>north;
map<pair<int, int>, pair<int, int>>west;
map<pair<int, int>, pair<int, int>>east;
pair<int, int> no_exist = make_pair(0, 0);
pair<int, int> s_find(pair<int, int> root)
{
    if (root != south[root]) south[root] = s_find(south[root]);
    return south[root];
}
pair<int, int> n_find(pair<int, int>root)
{
    if (root != north[root]) north[root] = n_find(north[root]);
    return north[root];
}
pair<int, int> w_find(pair<int, int>root)
{
    if (root != west[root]) west[root] = w_find(west[root]);
    return west[root];
}
pair<int, int> e_find(pair<int, int>root)
{
    if (root != east[root]) east[root] = e_find(east[root]);
    return east[root];
}
void n_join(pair<int, int>son1, pair<int, int>son2) // x小的当老大
{
    son1 = n_find(son1);
    son2 = n_find(son2);
    if (son1 != son2)
    {
        if (son1.first < son2.first)
            north[son2] = son1;
        else
            north[son1] = son2;
    }
}
void s_join(pair<int, int>son1, pair<int,int>son2) //x大的当老大
{
    son1 = s_find(son1);
    son2 = s_find(son2);
    if (son1 != son2)
    {
        if (son1.first > son2.first)
            south[son2] = son1;
        else
            south[son1] = son2;
    }
}
void w_join(pair<int, int>son1, pair<int, int>son2) //y小的当老大
{
    son1 = w_find(son1);
    son2 = w_find(son2);
    if (son1 != son2)
    {
        if (son1.second < son2.second)
            west[son2] = son1;
        else
            west[son1] = son2;
    }
}
void e_join(pair<int, int>son1, pair<int, int>son2) //y大的当老大
{
    son1 = e_find(son1);
    son2 = e_find(son2);
    if (son1 != son2)
    {
        if (son1.second > son2.second)
            east[son2] = son1;
        else
            east[son1] = son2;
    }
}
void update(pair<int, int>cur) //东南西北四个方向进行更新
{
    pair<int, int>next;
    next = make_pair(cur.first - 1, cur.second);
    north[cur] = cur;
    south[cur] = cur;
    west[cur] = cur;
    east[cur] = cur;
    if (north[next] != no_exist)
    {
        n_join(cur, next);
        s_join(cur, next);
    }
    next = make_pair(cur.first + 1, cur.second);
    
    if (south[next] != no_exist)
    {
        s_join(cur, next);
        n_join(cur, next);
    }
    next = make_pair(cur.first, cur.second - 1);
    
    if (west[next] != no_exist)
    {
        w_join(cur, next);
        e_join(cur, next);
    }
    next = make_pair(cur.first, cur.second + 1);
    
    if (east[next] != no_exist)
    {
        e_join(cur, next);
        w_join(cur, next);
    }
    return;
}
int main()
{
    int T,N, R, C;
    int x, y, cnt = 1;
    string order;
    cin >> T;
    pair<int, int> fa1, fa2, cur, next;
    while (T--)
    {
        cin >> N >> R >> C >> x >> y;
        cin >> order;
        south.clear();
        north.clear();
        east.clear();
        west.clear();
        cur.first = x;
        cur.second = y;
        update(cur);
        for (int i = 0; i < N; i++)
        {
            if (order[i] == 'N')
            {
                cur = n_find(cur);
                next = make_pair(cur.first - 1, cur.second);
            }
            else if (order[i] == 'S')
            {
                cur = s_find(cur);
                next = make_pair(cur.first + 1, cur.second);
            }
            else if (order[i] == 'W')
            {
                cur = w_find(cur);
                next = make_pair(cur.first, cur.second - 1);
            }
            else
            {
                cur = e_find(cur);
                next = make_pair(cur.first, cur.second + 1);
            }
            cur = next;
            update(cur);
        }
        printf("Case #%d: %d %d\n", cnt, cur.first, cur.second);
        cnt += 1;
    }
    return 0;
}

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值