首先:杭电这题数据可以加强(下面看我分析一下)
有两种思路:
1.优先队列+特判+vis 2.普通队列+全部做完再return
我先说一下我一开始错误的那种做法:普通队列+特判
因为平时做的bfs边的权重都是一样的,这应该是我用bfs做的第一个边权重不一定一样的题目。
所以我在bfs里的while循环里像之前一样,会加个特判:如果当前队头已经是终点,直接返回距离。
但我一开始也想过边的权重不一样这个问题,但当时我想的是该点上是否有守卫是确定的,有守卫就是有守卫,没有守卫就是没有守卫。所以直接bfs到最后,他肯定还是会更新成最优答案。
重点来了:我这个思路是没错的,但关键就在于我加了一个特判在while循环里,也就是只要遇到终点,我就直接return 距离了。关键在于我们按照当前搜索的路径搜到终点并不一定是最优的那条路径,所以我们一遇到终点就直接return的话就不一定是正确答案。
上面说了我一开始的思路没错,错就错在在while里加了一个特判。那正确做法应该是不在while里加特判,等while循环结束,也就是队列里没有元素,能更新的都更新成最优解后 return dist[终点]
一.给出优先队列做法的代码:(下面会分析)
#include<bits/stdc++.h>
using namespace std;
const int N = 210, INF = 0x3f3f3f3f;
typedef pair<int,int> PII;
int n, m;
int sx, sy;
char p[N][N];
int vis[N][N];
int dx[] = {-1, 0, 1, 0}, dy[]={0, 1, 0, -1};
struct state
{
int x, y, dist;
bool operator < (const state & a) const
{
return dist > a.dist;
}
} ;
bool check(int x, int y)
{
return x >= 0 && x < n && y >= 0 && y < m && p[x][y] != '#';
}
int bfs(int ex, int ey)
{
memset(vis, 0, sizeof vis);
priority_queue<state> q;
q.push(state{sx, sy, 0});
while(q.size())
{
auto t = q.top();
q.pop();
int x = t.x, y = t.y, dist = t.dist;
if(x == ex && y == ey) return dist;
for(int i = 0; i < 4; i ++ )
{
int rx = x + dx[i], ry = y + dy[i];
if(!check(rx, ry) || vis[rx][ry]) continue;
else
{
vis[rx][ry] = 1;
if(p[rx][ry] == 'x') q.push(state{rx, ry, dist + 2});
else q.push(state{rx, ry, dist + 1});
}
}
}
return INF;
}
int main()
{
while(scanf("%d%d", &n, &m) == 2)
{
vector<PII> v;
getchar();
for(int i = 0; i < n; i ++ )
{
for(int j = 0; j < m; j ++ )
{
scanf("%c", &p[i][j]);
if(p[i][j] == 'r') v.push_back({i,j});
if(p[i][j] == 'a') sx = i, sy = j;
}
getchar();
}
int res = INF;
for(auto a : v)
{
int ex = a.first, ey = a.second;
int ans = bfs(ex, ey);
res = min(res, ans);
}
if(res == INF) puts("Poor ANGEL has to stay in the prison all his life.");
else printf("%d\n", res);
}
return 0;
}
分析两点:
1.为什么优先队列就可以在while里加特判直接return
2.为什么要vis数组
1.上面说了普通队列按照当前搜索的路径搜到终点并不一定是最优的那条,最后一步加的都一样,区别就在于前面的dist不一样;但优先队列就解决了这个问题,优先队列按照距离从小到大排序,你放在队头的一定已经是当前最优解了,你再从最优解继续拓展出去还是最优解,因为最后一步加的都一样,你前面是最优,加了最后这一步还是最优,所以优先队列肯定是以最优路径搜索到终点的。所以这样就可以特判了,只要队头取出来后发现是终点,直接return dist[终点]就可以了
2.优先队列,确保你每次都是从最优路径开始往四个方向搜索,也就是你之前搜到过的话,之后再来搜肯定会比第一次小,所以要开个vis数组,确保是否已经搜到过,搜到就赋值成1,然后搜过就不用再对该点进行操作了
二.给出普通队列的代码
#include<bits/stdc++.h>
using namespace std;
const int N = 210, INF = 0x3f3f3f3f;
typedef pair<int,int> PII;
int n, m;
int sx, sy;
char p[N][N];
int dist[N][N];
int dx[] = {-1, 0, 1, 0}, dy[]={0, 1, 0, -1};
bool check(int x, int y)
{
return x >= 0 && x < n && y >= 0 && y < m && p[x][y] != '#';
}
int bfs(int ex, int ey)
{
memset(dist, 0x3f, sizeof dist);
dist[sx][sy] = 0;
queue<PII> q;
q.push({sx, sy});
while(q.size())
{
auto t = q.front();
q.pop();
int x = t.first, y = t.second;
for(int i = 0; i < 4; i ++ )
{
int rx = x + dx[i], ry = y + dy[i];
if(check(rx, ry) && dist[rx][ry] > dist[x][y] + 1)
{
if(p[rx][ry] == 'x') dist[rx][ry] = dist[x][y] + 2;
else dist[rx][ry] = dist[x][y] + 1;
q.push({rx, ry});
}
}
}
return dist[ex][ey];
}
int main()
{
while(scanf("%d%d", &n, &m) == 2)
{
vector<PII> v;
getchar();
for(int i = 0; i < n; i ++ )
{
for(int j = 0; j < m; j ++ )
{
scanf("%c", &p[i][j]);
if(p[i][j] == 'r') v.push_back({i,j});
if(p[i][j] == 'a') sx = i, sy = j;
}
getchar();
}
int res = INF;
for(auto a : v)
{
int ex = a.first, ey = a.second;
int ans = bfs(ex, ey);
res = min(res, ans);
}
if(res == INF) puts("Poor ANGEL has to stay in the prison all his life.");
else printf("%d\n", res);
}
return 0;
}
因为不是一遇到终点就是从最优路径搜索到的终点,所以等while循环结束,最后再return dist[终点],此时return 的必定是终点的最优解
三. 为什么说杭电该题数据需要加强呢
我给个数据,再给出一个错误代码
显然:正确答案应该是6
给出错误代码(即普通队列+特判):
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N = 210, INF = 0x3f3f3f3f;
int n, m;
int sx, sy;
char p[N][N];
int dist[N][N];
int dx[] = {-1, 0, 1, 0}, dy[]={0, 1, 0, -1};
bool check(int x, int y)
{
return x >= 0 && x < n && y >= 0 && y < m && p[x][y] != '#';
}
int bfs(int ex, int ey)
{
memset(dist, 0x3f, sizeof dist);
dist[sx][sy] = 0;
queue<PII> q;
q.push({sx, sy});
while(q.size())
{
auto t = q.front();
q.pop();
int x = t.first, y = t.second;
if(x == ex && y == ey) return dist[x][y];
for(int i = 0; i < 4; i ++ )
{
int rx = x + dx[i], ry = y + dy[i];
if(check(rx, ry) && dist[rx][ry] > dist[x][y] + 1)
{
if(p[rx][ry] == 'x') dist[rx][ry] = dist[x][y] + 2;
else dist[rx][ry] = dist[x][y] + 1;
q.push({rx, ry});
}
}
}
return INF;
}
int main()
{
while(scanf("%d%d", &n, &m) == 2)
{
vector<PII> v;
getchar();
for(int i = 0; i < n; i ++ )
{
for(int j = 0; j < m; j ++ )
{
scanf("%c", &p[i][j]);
if(p[i][j] == 'r') v.push_back({i,j});
if(p[i][j] == 'a') sx = i, sy = j;
}
getchar();
}
int res = INF;
for(auto a : v)
{
int ex = a.first, ey = a.second;
int ans = bfs(ex, ey);
res = min(res, ans);
}
if(res == INF) puts("Poor ANGEL has to stay in the prison all his life.");
else printf("%d\n", res);
}
return 0;
}
我们会发现用这个代码输出的答案是7,然后我用该代码交到杭电oj还是给我AC了
关键就在于如果是普通队列+特判的话:因为之前bfs都是没有边的权重,然后加特判;
所以这题这样做,他会输出步数最少到终点的答案(即拓展出去的次数少就到终点),然后这个时候如果在这条步数“最小”的路径上放了很多守卫,其他虽然拓展到终点的步数大,但路途上没有守卫。你这样就会输出步数最少到终点的那个dist,但并不一定是最优解。
...........................................................................................................................................................