题目描述
题解
刚看到这道题的时候想的很复杂,感觉每一次都有前提不是很好搞,但其实这三种都可以直接用最短路来实现。
关键是怎么实现呢?第一次写的时候是做了三遍spfa,每一次求出答案了之后再跑下一次,让下一次满足当前的条件。但是写完之后发现是不对的。显然不对啊!因为当前在跑的时候你无法保证用过的点都是上一问合法的答案。恰恰相反!我们应该在一次spfa里处理这三个问题,只不过是优先级不同而已!
这三问优先级依次递减,所以只有在优先级高的答案相同的时候才去考虑更新优先级低的。这样做一遍spfa就能保证在满足优先级高的同时尽量满足优先级低的,就非常科学了。
不过后来我又仔细想了一下,其实做三遍spfa也是可以判的。就是当f[now]+len==f[nxt]的时候,说明now在s到nxt的最短路上,这样的时候再去更新。这种判断点是否在最短路上的方法是非常常见的呀!真是傻逼了。
代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
#define N 50
#define LL long long
int n,m,sx,sy,tx,ty,minadd,minstep,cnt,inf;
int a[N][N],add[N][N],step[N][N];
LL sol[N][N];
bool vis[N][N];
struct hp{int x,y;};
int dx[8]={-2,-2,-1,-1,1,1,2,2};
int dy[8]={-1,1,-2,2,-2,2,-1,1};
queue <hp> q;
void spfa()
{
memset(add,127,sizeof(add));inf=add[0][0];add[sx][sy]=0;
memset(step,127,sizeof(step));step[sx][sy]=0;
memset(sol,0,sizeof(sol));sol[sx][sy]=1;
memset(vis,0,sizeof(vis));vis[sx][sy]=true;
while (!q.empty()) q.pop();q.push((hp){sx,sy});
while (!q.empty())
{
hp now=q.front();q.pop();
vis[now.x][now.y]=false;
for (int i=0;i<8;++i)
{
int x=now.x+dx[i],y=now.y+dy[i];
if (!(x>=1&&x<=n&&y>=1&&y<=m)||a[x][y]==2) continue;
int len;
if (a[x][y]==0) len=1; else len=0;
if (add[now.x][now.y]+len<add[x][y])
{
add[x][y]=add[now.x][now.y]+len;
step[x][y]=step[now.x][now.y]+1;
sol[x][y]=sol[now.x][now.y];
if (!vis[x][y])
{
vis[x][y]=true;
q.push((hp){x,y});
}
}
else if (add[now.x][now.y]+len==add[x][y])
{
if (step[now.x][now.y]+1<step[x][y])
{
step[x][y]=step[now.x][now.y]+1;
sol[x][y]=sol[now.x][now.y];
if (!vis[x][y])
{
vis[x][y]=true;
q.push((hp){x,y});
}
}
else if (step[now.x][now.y]+1==step[x][y])
{
sol[x][y]+=sol[now.x][now.y];
if (!vis[x][y])
{
vis[x][y]=true;
q.push((hp){x,y});
}
}
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
for (int j=1;j<=m;++j)
{
scanf("%d",&a[i][j]);
if (a[i][j]==3) sx=i,sy=j;
if (a[i][j]==4) tx=i,ty=j;
}
spfa();
if (add[tx][ty]==inf)
{
puts("-1");
return 0;
}
printf("%d\n%d\n%lld\n",add[tx][ty],step[tx][ty],sol[tx][ty]);
}