洛谷P2411 【[USACO07FEB]Silver Lilypad Pond S】

思路

首先看到这个题,思路很简单,非常明显的最短路求解,第一问基本就是个板子题,只不过将存图改为了马走日和其他限制条件而已。我们可以看作将每一步的起点和终点连边,若是莲花,则不需要放置,边权就是0,如果是水,那么则需要放置,边权就是1。如果是岩石,可以将边权设置为-1,意思就是不能走。第二问意思就是问你,在走了最短路的情况下(放最少的莲花),跳了几步。这个其实也很简单,多bfs一遍,根据情况分类讨论即可。如果放置的莲花少那么直接覆盖,如果莲花一样则比较步数。第三问还是这样,如果可以更新就直接覆盖,不能的话就加上方案数。

但是显然,三遍bfs太难写了,我看到有些题解直接bfs三遍,我人都傻了,巨佬果然有耐心。但其实仔细一想,这些其实都可以用一遍bfs搞定。更新第一问,也就是莲花最少放置数,其实就是板子题,直接bfs即可。不要告诉我你不会bfs(其实就是SPFA)。第二问我们可以在bfs的时候多分类讨论一下。我们更新最短路(放置莲花数)的最短路(步数),有两种情况:1.莲花数更新了,由于我们重视的是莲花数尽可能少,所以即使走了很多步,也要选它。2.莲花数一样,但是新的走法步数更少,那么也需要更新。第三问的话其实更简单,只需要再加上一种情况即可:当且仅当莲花数和步数都相等时,终点的方案数要加上起点的方案数。其它更新时都直接覆盖即可,因为如果可以更新那么一定是一种新的走法,目前只有这一种可以,所以直接覆盖即可,这样就切掉了这道最短路板子题。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long int ll;
ll n,m,a,sx,sy,lx,ly,b[40][40],d1[40][40],d2[40][40],sum[40][40];//d1[i][j]表示到点(i,j)的最少莲花放置数,d2[i][j]表示到点(i,j)在保证莲花放置数最少时所用的最少步数,sum[i][j]表示当莲花放置数和步数最少时的方案数有多少,b[i][j]则是存储点(i,j)是莲花还是水还是岩石 
bool v[40][40];//bfs记录是否入队 
ll dx[8]={1,1,2,2,-1,-1,-2,-2},dy[8]={2,-2,1,-1,2,-2,1,-1};//模拟走法 
queue<pair<ll,ll> >q;
int main()
{
	scanf("%lld%lld",&n,&m);
	for(ll i=1;i<=n;i++){
		for(ll j=1;j<=m;j++){
			scanf("%lld",&a);
			if(a==0){
				b[i][j]=1;
			}
			if(a==1){
				b[i][j]=0;
			}
			if(a==2){
				b[i][j]=-1;
			}
			if(a==3){
				sx=i;
				sy=j;
			}
			if(a==4){
				lx=i;
				ly=j;
			}
		}
	}//存储 
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			d1[i][j]=10000000;
		}
	}//没有用memset,便于检验是否存在最短路 
	memset(d2,0x3f,sizeof(d2));
	memset(v,0,sizeof(v));
	q.push(make_pair(sx,sy));
	d1[sx][sy]=0;
	d2[sx][sy]=0;
	sum[sx][sy]=1;
	v[sx][sy]=1;//初始化,记住方案数一定要初始化为1 
	while(!q.empty()){
		int x=q.front().first;
		int y=q.front().second;
		v[x][y]=0;
		q.pop();
		for(int i=0;i<8;i++){
			if(x+dx[i]>=1&&x+dx[i]<=n&&y+dy[i]>=1&&y+dy[i]<=m&&b[x+dx[i]][y+dy[i]]!=-1){//如果可以走过去 
				if(d1[x][y]+b[x+dx[i]][y+dy[i]]==d1[x+dx[i]][y+dy[i]]){
					if(d2[x][y]+1==d2[x+dx[i]][y+dy[i]]){
						sum[x+dx[i]][y+dy[i]]+=sum[x][y];//若多了一种走法可以保持莲花数和步数都最小,则加和方案数 
					}
					if(d2[x][y]+1<d2[x+dx[i]][y+dy[i]]){//莲花数相同但可以更新步数 
						d2[x+dx[i]][y+dy[i]]=d2[x][y]+1;
						sum[x+dx[i]][y+dy[i]]=sum[x][y];
						if(!v[x+dx[i]][y+dy[i]]){
							q.push(make_pair(x+dx[i],y+dy[i]));
							v[x+dx[i]][y+dy[i]]=1;
						}	
					}
				}
				if(d1[x][y]+b[x+dx[i]][y+dy[i]]<d1[x+dx[i]][y+dy[i]]){//若可以更新莲花数则所有的都要更新,因为莲花数是最重要的 
					d1[x+dx[i]][y+dy[i]]=d1[x][y]+b[x+dx[i]][y+dy[i]];
					d2[x+dx[i]][y+dy[i]]=d2[x][y]+1;
					sum[x+dx[i]][y+dy[i]]=sum[x][y];
					if(!v[x+dx[i]][y+dy[i]]){
						q.push(make_pair(x+dx[i],y+dy[i]));
						v[x+dx[i]][y+dy[i]]=1;
					}
				}
			}
		}
	}
	if(d1[lx][ly]==10000000){//若没更新则输出-1 
		printf("-1
");
		return 0;
	}
	printf("%lld
",d1[lx][ly]);
	printf("%lld
",d2[lx][ly]);
	printf("%lld
",sum[lx][ly]);//否则对应输出三个数组里存的答案 
	return 0; 
}

总结

其实这个题可以理解为多维的最短路算法,我觉得就类似于是二维费用的背包,多开几个数组或者多开几个维度就能解决掉,不是很难,只要深刻理解最短路算法即可。

最后,不开longlong见祖宗

原文地址:https://www.cnblogs.com/57xmz/p/13456123.html