zoukankan      html  css  js  c++  java
  • 洛谷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见祖宗

  • 相关阅读:
    hdu 2196(树上点分治)
    hdu 4807(网络流 + 贪心)
    hdu4101
    hdu4216
    hdu 4219, 树形概率DP
    hdu 4127 A*搜索
    hdu 4126
    hdu 5296,15年多校1-7
    poj3436 ACM Computer Factory
    Fence
  • 原文地址:https://www.cnblogs.com/57xmz/p/13456123.html
Copyright © 2011-2022 走看看