题目描述
助教是个小可怜,因为她不知道要出什么机考题了。然后她发现大家上周学了搜索,欣喜若狂。那一天刚好是大选之日,她在搜索引擎输入了election 2020,开始关注自己还能不能出国读书。选情十分焦灼,小可怜越看越紧张,一怒之下关闭网页开始睡回笼觉。小可怜希望在梦中能获得操纵大选的能力。
日有所思,日有所梦。小可怜梦到自己来到了一个国家USB(United States of Bairimeng),这个国家由 m − 1 m - 1 m−1条经线和 m − 1 m - 1 m−1条纬线分隔,一共拥有 m × m m \times m m×m个州,每个州是红州,蓝州,摇摆州中的一种。一位叫做Truden的人想要找一条路,从USB最西北角的州走到最东南角的州。
任何一个时刻,Truden不被允许出现在摇摆州。Truden只能往上下左右四个方向的州前进,当Truden从一个州走到另一个州,如果两个州的颜色相同,那么Truden不需要支付过路费,否则Truden需要支付1USB元的过路费。
但不能取道摇摆州使Truden十分苦恼,于是Truden找上了在USB做大法师的小可怜。每次只要支付2USB元,小可怜就会帮Truden暂时把一个摇摆州变成红州或者蓝州中的一种。但是要注意的是,小可怜使用魔法后,Truden可以入境一个被变色的摇摆州,但当Truden还在此州境内时,小可怜不会帮Truden把另一个摇摆州也变色,直到Truden离开此州,小可怜才同意会帮Truden再次施法。而当Truden离开了变色了的摇摆州后,这个州又将变成摇摆州。
请你帮Truden计算一下,他最少要花费多少USB元,才能从西北角的州走到东南角的州。
输入格式
第一行包含两个正整数 m , n m,n m,n,以一个空格分开,分别代表USB拥有 m × m m \times m m×m个州,以及非摇摆州的数量是 n n n
接下来的nn行,每行三个整数 x , y , c x,y,c x,y,c,相邻的两个整数由一个空格隔开,分别表示坐标为 ( x , y ) (x,y) (x,y)的州的立场。坐标为 ( x , y ) (x,y) (x,y)的州表示,从北往南数第 x x x行,从西往东数第 y y y列的那个州。如果 c = 1 c = 1 c=1,此州是蓝州, c = 0 c = 0 c=0,此州是红州。
而其他的州均为摇摆州。数据保证西北角的州(即坐标为 ( 1 , 1 ) (1,1) (1,1)的州)不是摇摆州。
输出格式
一个整数,表示花费的USB元的最小值。如果Truden无法到达东南角的州,请输出-1。
样例输入
样例1
5 7
1 1 0
1 2 0
2 2 1
3 3 1
3 4 0
4 4 1
5 5 0
样例2
5 5
1 1 0
1 2 0
2 2 1
3 3 1
5 5 0
样例输出
样例1
8
样例2
-1
数据范围
对于 30 % 30\% 30%的数据, 1 ≤ m ≤ 5 , 1 ≤ n ≤ 10 1 \leq m \leq 5,1 \leq n \leq 10 1≤m≤5,1≤n≤10。
对于 60 % 60\% 60%的数据, 1 ≤ m ≤ 20 , 1 ≤ n ≤ 200 1 \leq m \leq 20,1\leq n \leq 200 1≤m≤20,1≤n≤200。
对于 100 % 100\% 100%的数据, 1 ≤ m ≤ 100 , 1 ≤ n ≤ 4 , 000 1 \leq m \leq 100,1 \leq n \leq 4,000 1≤m≤100,1≤n≤4,000。
小可怜眼看各州转红,一怒之下把全国都变成蓝色的了!(此行为不影响本题,请按题面做题)
题目彩蛋
白日梦帝国你发现了吗?
Truden你发现啦吗?Trump + Baiden
题目答疑
首先,题目明确说了,这是一道搜索的题目,那还是回到我们之前所学的广度优先搜素和深度优先搜索。我们来看看可能遇到的问题。
- 问题一:这道题到底是用BFS还是用DFS?
- 解答:首先实践证明【DFS】、【BFS】均可以完成这道题目!证明如下:显然你已经猜出来哪个时间是【DFS】哪个是【BFS】的结果。
评测编号 | 用户 | 题目名称 | 评测状态 | 运行时间 | 内存 | 语言 | 提交时间 |
---|---|---|---|---|---|---|---|
49729 | Musicminion | 1037. 小可怜操纵大选 | Accepted | 98ms | 32248KiB | C++ | Jul-28-2021 14:18:49 |
49728 | Musicminion | 1037. 小可怜操纵大选 | Accepted | 710ms | 33000KiB | C++ | Jul-28-2021 13:07:43 |
个人认为,只要[BFS]可以做的题目,[DFS]一定可以做,反之亦然。我们还是从图的角度看看。下图是DFS函数搜索的时候函数递归的运行情况,那么,如果DFS可以的话,我们可以假设一个队列,把第二层函数要递归的全部放入队列,然后一次按照队列执行,第三层同理,这样我们就把[DFS]变成了[BFS],而且优化了时间。
- 问题二:如何选择BFS还是用DFS?
- 解答:第一,取决于你会不会队列。(这个划掉,不过如果刚刚学习程序设计的同学可以优先考虑DFS)毕竟按照教材内容【程序设计思想与方法(翁慧玉)】的编排,确实先学习的函数递归,队列的知识要到数据结构才会学习,不过个人认为队列的知识可以自己学会,参考之前我的文章【DFS】【队】知识的传送门;第二,取决于搜索的选择,比如想这种,每次可以走的只有周围四个方向,完全是可以BFS的,但是,如果每次可以走的方向非常多【我们假设】,多到可能会把这个队列撑爆了,那显然不可以BFS。
- 问题三:BFS、DFS的核心是什么?
- 解答:是防止重复搜索,或者说陷入死循环。有下面几种可能方法,
【方法一】我们开一个bool
数组,一边搜索,只要搜索到就做好标记,每次搜索前检测是否之前已经搜索过。
【方法二】不开bool
数组,利用数据的比较,开始的时候把用来记录 关键信息 [例如步数、花费的钱] 的数组全部初始化为INT_MAX
[注意,此时不要对这个数组的元素进行加法或者其他运算,会爆INT
的!],然后每次只要输入的数据比之前的小,就允许输入。或者你初始化为一个很大值也可以。
【比较】两种方法显然第二种更好,因为 如果从其他路径搜索,搜索到了之前已经搜索的点,但是发现有更优解,这个时候方法二可以更新之前的结果!
题目解答
【DFS】的解答:
#include <iostream>
using namespace std;
int dx[]={0,-1,0,1};
int dy[]={1,0,-1,0};
int m,n;
int ans = 99999;
int x, y, c;
int path[101][101];
int best[101][101];
void dfs(int sx, int sy ,int flag , int price)
{
if (sx == m && sy ==m)
{
ans = min(ans, price);
best[m][m] = price;
return;
}
if (price>=best[sx][sy])
return;
best[sx][sy]=price;
for(int i=0 ;i<4 ;i++)
{
int tx = sx + dx[i];
int ty = sy + dy[i];
if (tx < 1 || ty < 1 || tx > m || ty > m)
{
continue;
}
if (path[tx][ty]) // 目前去的不是摇摆州
{
if (path[tx][ty] == path[sx][sy])
dfs(tx, ty, false, price);
else
dfs(tx, ty, false, price + 1);
}
else if (!flag)
// 目前去的是摇摆州 flag表示当前是否在摇摆州,!flag表示当前不在摇摆州
{
path[tx][ty]= path[sx][sy];
dfs(tx, ty, true, price + 2);
path[tx][ty] = false;
}
}
}
int main()
{
cin>>m>>n;
for(int i=1 ;i<=m;i++)
{
for(int j=1 ;j<=m;j++)
{
best[i][j]=999;
}
}
for(int i=1;i<=n;i++)
{
cin>>x>>y>>c;
path[x][y] = c+1;
}
dfs(1,1, false,0);
if (ans == 99999)
cout << -1 << endl;
else
cout << ans << endl;
for (int i = 1; i <= m; i++)
{
for (int j = 1; j <= m; j++)
{
cout << path[i][j] << "\t";
}
cout << endl;
}
cout << endl;
for (int i = 1; i <= m; i++)
{
for (int j = 1; j <= m; j++)
{
cout << best[i][j] << "\t";
}
cout << endl;
}
system("pause");
return 0;
}
【BFS】的解答:其实我是根据DFS的改编的
#include <iostream>
#include <queue>
using namespace std;
class node
{
public:
int x;
int y;
bool ifSwing;
int price;
int lastStatus;
};
int dx[4] = {0, -1, 0, 1};
int dy[4] = {1, 0, -1, 0};
int m,n;
int ans = 99999;
int x, y, c;
int path[101][101];
int best[101][101];
node departure, destination;
void bfs(node departure)
{
node np, nxp;
queue<node> bfsque;
bfsque.push(departure);
while(!bfsque.empty())
{
np = bfsque.front();
bfsque.pop();
if (np.price >= best[np.x][np.y])
{
continue;
}
else
best[np.x][np.y] = np.price;
for (int i = 0; i < 4; i++)
{
nxp.x = np.x + dx[i];
nxp.y = np.y + dy[i];
if (nxp.x < 1 || nxp.x > m || nxp.y < 1 || nxp.y > m)
{
continue;
}
if (np.x == m && np.y == m)
{
ans = min(ans, np.price);
best[m][m] = ans;
}
if (path[nxp.x][nxp.y] > 0 && np.ifSwing == false)
{
if (path[nxp.x][nxp.y] == path[np.x][np.y])
{
nxp.ifSwing = false;
nxp.price = np.price;
nxp.lastStatus = 0;
bfsque.push(nxp);
}
else
{
nxp.ifSwing = false;
nxp.price = np.price + 1;
nxp.lastStatus = 0;
bfsque.push(nxp);
}
}
if (path[np.x][np.y] != 0 && path[nxp.x][nxp.y] == 0)
{
nxp.ifSwing = true;
nxp.lastStatus = path[np.x][np.y];
nxp.price = np.price + 2;
bfsque.push(nxp);
}
if (path[nxp.x][nxp.y] > 0 && np.ifSwing == true)
{
nxp.ifSwing = false;
if (np.lastStatus == path[nxp.x][nxp.y])
nxp.price = np.price;
else
nxp.price = np.price + 1;
nxp.lastStatus = 0;
bfsque.push(nxp);
}
}
}
}
int main()
{
cin >> m >> n;
for(int i=1 ;i<=m;i++)
{
for(int j=1 ;j<=m;j++)
{
best[i][j]=99999;
}
}
for(int i=1;i<=n;i++)
{
cin >> x >> y >> c;
path[x][y] = c + 1;
}
departure.x = 1;
departure.y = 1;
departure.ifSwing = false;
departure.lastStatus = 0;
departure.price = 0;
bfs(departure);
if (ans == 99999)
cout << -1 << endl;
else
cout << ans << endl;
system("pause");
return 0;
}