上海市计算机学会竞赛平台2023年8月月赛丙组 T5 方格路径
T5 方格路径
内存限制: 256 Mb时间限制: 1000 ms
题目描述
给定 n × m n×m n×m 个方格构成的图,每个格子都有一种地形:
有一些格子是墙,以符号 # 表示,墙不可通行。
有一些格子是空地,以符号 . 表示,空地可以通行。
请统计从左上角的方格出发,有多少种不同的路线可以以最短距离走到右下角。在行走过程中,不能进入地形为墙的方格,保证起点与终点方格地形不是墙。且行走时,只能移动到水平或垂直方向相邻的方格。
由于方案数可能很大,输出模 1 , 000 , 000 , 007 1,000,000,007 1,000,000,007 的余数。
输入格式
第一行:单个整数 n n n 与 m m m
第二行到第 n + 1 n+1 n+1 行:第 i + 1 i+1 i+1 行每行有 m m m 个整数表示第 i i i 行的地形。
输出格式
单个整数:表示路线方案模 1 , 000 , 000 , 007 1,000,000,007 1,000,000,007 的余数。
数据范围
30% 的数据, 1 ≤ n , m ≤ 4 1≤n,m≤4 1≤n,m≤4
60% 的数据, 1 ≤ n , m ≤ 10 1≤n,m≤10 1≤n,m≤10
100% 的数据, 1 ≤ n , m ≤ 1000 1≤n,m≤1000 1≤n,m≤1000
样例数据
输入:
3 3
…
.#.
…
输出:
2
题目给定了  
     
      
       
       
         n 
        
       
         × 
        
       
         m 
        
       
      
        n×m 
       
      
    n×m 的图形,‘#’是障碍,‘.’是通路。
 而与普通的最优路径问题不同的是此题问的是最优路径的条数。
方法一:宽搜找最短路径步数+深搜搜索路径条数(70分)
深搜中的剪枝与最优化剪枝不同,当步数相等时最优化剪枝会剪掉,因为要寻找的是路径长度,但是现在找的是路径条数,所以不能剪。
 另外,结构体中的  
     
      
       
       
         p 
        
       
         o 
        
       
         s 
        
       
      
        pos 
       
      
    pos 表示步数, 
     
      
       
       
         x 
        
       
      
        x 
       
      
    x 与  
     
      
       
       
         y 
        
       
      
        y 
       
      
    y 表示坐标。
#include <bits/stdc++.h>
#define int long long
#define inf 1000000007
using namespace std;
int n,m,dp[1010][1010],dx[4]={0,1,0,-1},dy[4]={1,0,-1,0},num,cnt;
char a[1010][1010];
struct nod{
	int x,y,pos;	
}q[1000010];
int bfs(){
	int head=1,tail=2;
	q[1].x=1,q[1].y=1,q[1].pos=0;
	while(head<tail){
		if(q[head].x==n&&q[head].y==m)return q[head].pos;
		int x=q[head].x,y=q[head].y;
		for(int i=0;i<4;++i){
			int xx=x+dx[i],yy=y+dy[i];
			if(xx>0&&xx<=n&&yy>0&&yy<=m&&dp[xx][yy]==-1&&a[xx][yy]=='.'){
				q[tail].x=xx,q[tail].y=yy,q[tail].pos=q[head].pos+1;
          		dp[xx][yy]=1;
				++tail;
			}
		}
		++head;
	}
}
void f(int x,int y,int s){
	if(s>=num){
      if(x==n&&y==m&&s==num)cnt=(cnt+1)%inf;
      return;
    }
	dp[x][y]=s;
	for(int i=0;i<4;++i){
		int xx=x+dx[i],yy=y+dy[i];
		if((xx>0&&xx<=n&&yy>0&&yy<=m)&&(dp[xx][yy]==-1||dp[xx][yy]>=s+1)&&a[xx][yy]=='.')
			f(xx,yy,s+1);
	}
}
signed main(){
	memset(dp,-1,sizeof(dp));
	cin>>n>>m;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			cin>>a[i][j];
	num=bfs();
	memset(dp,-1,sizeof(dp));
	f(1,1,0);
	cout<<cnt<<endl;
	return 0;
}
 
此时TLE三个点。
方法2:宽搜路径(STL栈)(70分)
如果简单粗暴开个10,000,000数组肯定因数组不够越界而RE,所以在用了STL栈之后变为TLE。
 其本质仍然是枚举每一条路径并计数,题目却要求模一个高达10亿的数,由此可见最坏计数次数必定大于10亿,超时不可避免。注意剪枝与上文同。
 而现在的宽搜程序正是答案的基础。
#include <bits/stdc++.h>
#define int long long
#define inf 1000000007
using namespace std;
int n,m,dp[1010][1010],dx[4]={0,1,0,-1},dy[4]={1,0,-1,0},num=LONG_LONG_MAX,ans;
char a[1010][1010];
struct nod{
	int x,y,pos;	
};
queue<nod>q;
void bfs(){
	q.push({1,1,0});
	while(!q.empty()){
		if(q.front().x==n&&q.front().y==m&&q.front().pos<=num){
			num=q.front().pos;
			ans=(ans+1)%inf;
		}
		int x=q.front().x,y=q.front().y;
		if(q.front().pos<num)
			for(int i=0;i<4;++i){
				int xx=x+dx[i],yy=y+dy[i];
				if(xx>0&&xx<=n&&yy>0&&yy<=m&&(dp[xx][yy]==-1||dp[xx][yy]==q.front().pos+1)&&a[xx][yy]=='.'){
					q.push({xx,yy,q.front().pos+1});
					dp[xx][yy]=q.back().pos;
				}
			}
		q.pop();
	}
}
signed main(){
	memset(dp,-1,sizeof(dp));
	cin>>n>>m;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			cin>>a[i][j];
	bfs();
	cout<<ans<<endl;
	return 0;
}
 
注意:Dev-c++ 5.5.2 可能会报警(入队写法),但是网站交是可以过的。不放心的同学可以手写自构函数。
方法3:宽搜改良(AC)
首先需要明白的是,因为宽搜的基本思想,所以步数消耗较大的状态一定在消耗少的后面,所以当枚举到第二个状态重复的点时前一个肯定还未出队,所以就可以把它们合并为一个状态。
 定义结构体时新增  
     
      
       
       
         s 
        
       
         t 
        
       
         e 
        
       
         p 
        
       
      
        step 
       
      
    step 变量用于存储当前状态的方案数,遇到状态重复时就可以将两个  
     
      
       
       
         s 
        
       
         t 
        
       
         e 
        
       
         p 
        
       
      
        step 
       
      
    step 加到一起,合并在一个空间内。此时剪枝就可以改为普通宽度优先搜索的剪枝(因为每一个点分为三种情况:
 1.第一次到达,单独占用空间。
 2.步数消耗一样小合并状态。
 3.步数消耗大,剪掉。)
#include <bits/stdc++.h>
#define int long long
#define inf 1000000007
using namespace std;
int n,m,dp[1010][1010],dx[4]={0,1,0,-1},dy[4]={1,0,-1,0},num=LONG_LONG_MAX,ans,vh[1010][1010];
char a[1010][1010];
struct nod{
	int x,y,pos,step;	
}q[1000010];
void bfs(){
	int head=1,tail=2;
	q[1].x=1,q[1].y=1,q[1].pos=0,q[1].step=1;
	while(head<tail){
		if(q[head].x==n&&q[head].y==m&&q[head].pos<=num){
			num=q[head].pos;
			ans=(ans+q[head].step)%inf;
		}
		int x=q[head].x,y=q[head].y;
		if(q[head].pos<num)
			for(int i=0;i<4;++i){
				int xx=x+dx[i],yy=y+dy[i];
				if(xx>0&&xx<=n&&yy>0&&yy<=m&&(dp[xx][yy]==-1||dp[xx][yy]==q[head].pos+1)&&a[xx][yy]=='.'){
					if(dp[xx][yy]==q[head].pos+1)q[vh[xx][yy]].step=(q[vh[xx][yy]].step+q[head].step)%inf;
					else{
						vh[xx][yy]=tail;
						q[tail].x=xx,q[tail].y=yy,q[tail].pos=q[head].pos+1,q[tail].step=q[head].step;
						dp[xx][yy]=q[tail].pos;
						++tail;
					}
				}
			}
		++head;
	}
}
signed main(){
	memset(dp,-1,sizeof(dp));
	cin>>n>>m;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			cin>>a[i][j];
	bfs();
	cout<<ans<<endl;
	return 0;
}
                        

                
            
        
浙公网安备 33010602011771号