啦啦啦,我来更新啦。最近把毕设做完了,然后开始做google的kick start了。虽然暂时不找工作,但是打了kick start可以方便参加google的其他活动(比如冬令营等等)。而作为一只算法的菜鸡,也要开始慢慢飞啦!
这道题是本轮kick start里面最简单的一道题,也是可以直接想出来的。
【题意】一个R*C的迷宫,给定你起点。给定一段指令,指令有四种,N,S,W,E,分别代表着向北走(x坐标-1),向南走(x坐标+1),向西走(y坐标-1),向东走(y坐标+1)。如果走到了之前走过的格子,就继续往当前指令的方向走,直到走到一个之前没有访问过的格子,(注:所有的指令都不会指引你走出迷宫的边界)。问,经过这么一段指令后,你能到达哪个格子。
【输入】T代表测试用例的个数,每个测试用例的第一行为,分别代表指令的个数,迷宫的行数,迷宫的列数,起点的横坐标,起点的纵坐标。第二行为一行指令,仅由上述4种指令构成。0<
<=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;
}