池塘

青蛙过河问题的动态规划解法
博客围绕青蛙过河问题展开,将池塘看作n×m矩形,青蛙需踩荷叶到对岸。介绍了问题描述、输入输出格式及样例。指出该问题是动态规划问题,设计状态dp[i][j][k][l],通过翻转图让两只青蛙从一边出发,还添加剪枝条件,最后循环更新状态累加方案数。

Description

从前有两个青蛙王国,两个王国商业都非常繁荣。但是一块池塘阻碍了两国的商业往来。一次,两只青蛙在池塘的两岸,他们都希望到对岸去。我们可以将池塘看做一个 n×mn×mn×m 的矩形,在每个格子里,可能会有荷叶。青蛙必须踩在荷叶上,不能跳进水里。如图青蛙可以向他前方的5个有荷叶的地方跳去。
在这里插入图片描述
由于有的地方荷叶比较小,当一个青蛙从该荷叶上跳走之后,荷叶会沉入水底,两个青蛙也不能同时跳上这种荷叶。两个青蛙想知道有多少种方式使他们都到达对岸。第一个青蛙可以从第一行任何一个有荷叶的格子出发。第二个青蛙可以从最后一行任何一个有荷叶的格子出发。当第一个青蛙到达最后一行任何一个有荷叶的格子时,他就算到达了对岸。当第二个青蛙到达第一行任何一个有荷叶的格子时,他也算到达了对岸。请你帮助青蛙们计算有多少种方案可以让他们都到达对岸。
注:第一个青蛙只能向下跳,第二个青蛙只能向上跳。青蛙并不能跳出矩形区域。

Input

输入的第一行包含两个整数 nnnmmm
第2至 n+1n+1n+1 行包含 mmm 个整数。若该数为0,表示该格子上没有荷叶,青蛙不能通过。若该数为1,表示该格子上的荷叶只允许一个青蛙通过。若该数为2,表示该格子上的荷叶可以允许两个青蛙都通过。

Output

输出的第一行包含一个整数,表示两个青蛙都到达对岸的方案数。由于结果可能非常大,输出答案模1000000007的结果。

Sample Input

Test Case #1

4 1
2
0
0
2

Test Case #2

5 3
1 0 1
0 0 0
0 1 0
0 0 0
1 0 1

Test Case #3

4 3
1 1 0
0 0 0
0 0 1
2 0 0

Sample Output

Test Case #1

1

Test Case #2

0

Test Case #3

2

Data

对于30%的数据:n,m≤10n , m \leq 10n,m10
对于另外20%的数据,格子只有0或2两种
对于100%的数据,n,m≤50n , m \leq 50n,m50

Associate

看样子这道题暴力的分数并不是很多。不如想想正解
但是非常抱歉,我考试的时候只剩下15min了。真是失误,我获得了0分的好成绩。因为根本就没怎么想,没时间了,样例都没测就交了

Solution

直接来说正解怎么做吧:
很简单能发现,这道题应该是一个动态规划题。我们考虑设计一个状态:
dp[i][j][k][l]dp[i][j][k][l]dp[i][j][k][l] 表示当第一支青蛙走到 (i,j)(i , j)(i,j) ,第二支青蛙走到 (k,l)(k , l)(k,l) 的时候的状态数目。

这里有一个常用的技巧:因为图是可以颠倒的,但是一个从上面走,一个从下面走,不方便计数,那么可以把图翻个180度,相当于两只青蛙都从一边出发,方案数必定是一样的

考虑这个dp方程。实际上还是挺好想的。但是其中可以添加一个剪枝,就是 kkk 一定要在 i−3i - 3i3i+3i + 3i+3 之间。因为这样的话,他们差的不会很远,否则可能会导致一只青蛙没地方走了的情况,只能原地不动。这是不能允许的。

剩下的就非常简单了。我们循环一下5种转移方式(就是青蛙的跳动方式),分别更新。
这里要注意,只更新当前行数更小的那只青蛙,让他往前面跳,永远都是慢的在追快的,否则会越差越多,最后超出了3的范围,就不能更新了。
在方程中不断累加,最后把都在最后一行的两只青蛙的方案数加起来就行了。

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cmath>

using namespace std;

inline int read() {
    int x = 0 , f = 1; char ch = getchar();
    for ( ; !isdigit(ch) ; ch = getchar()) if (ch == '-') f = -1;
    for ( ; isdigit(ch) ; ch = getchar()) x = x * 10 + ch - '0';
    return x * f;
}

const int maxn = 51;
const int mod = 1e9 + 7;

const int maxDir = 5;
const int dirx[maxDir] = {1 , 1 , 2 , 2 , 3}; 
const int diry[maxDir] = {-2 , 2 , -1 , 1 , 0};

int n , m;

int _map[maxn][maxn];

int dp[maxn][maxn][maxn][maxn];

int ans;

int main() {
	n = read() , m = read();
	for (int i = 1 ; i <= n ; i ++) {
		for (int j = 1 ; j <= m ; j ++) {
			_map[i][j] = read();
		}
	}
	
	for (int i = 1 ; i <= m ; i ++) {
		for (int j = 1 ; j <= m ; j ++) {
			if (i == j && _map[1][i] == 2) {//两只青蛙一起站在一个为2的荷叶上
				dp[1][i][1][i] = 1;
			}
			else if (i != j && _map[1][i] && _map[1][j]) {//两只青蛙分别站在不是水的两片荷叶上
				dp[1][i][1][j] = 1;
			}
		}
	}
	
	for (int i = 1 ; i <= n ; i ++) {
		for (int j = 1 ; j <= m ; j ++) {
			for (int k = max(1 , i - 3) ; k <= min(n , i + 3) ; k ++) {
				for (int l = 1 ; l <= m ; l ++) {
					if (dp[i][j][k][l]) {
						
						if (i >= k) {
							for (int dir = 0 ; dir < maxDir ; dir ++) {
								int newx = k + dirx[dir] , newy = l + diry[dir];
								
								if (newx <= 0 || newx > n || newy <= 0 || newy > m) { //越界
									continue;
								}
								
								if (!_map[newx][newy]) { //下面要去的点是水
									continue;
								}
								
								if (newx == i && newy == j && _map[i][j] == 1) {//两只青蛙一起到了一个只能装1个青蛙的荷叶上
									continue;
								}
								
								dp[i][j][newx][newy] = (dp[i][j][newx][newy] + dp[i][j][k][l]) % mod;//累加
							}
						}
						else {//同理
							for (int dir = 0 ; dir < maxDir ; dir ++) {
								int newx = i + dirx[dir] , newy = j + diry[dir];
								
								if (newx <= 0 || newx > n || newy <= 0 || newy > m) {
									continue;
								}
								
								if (!_map[newx][newy]) {
									continue;
								}
								
								if (newx == k && newy == l && _map[k][l] == 1) {
									continue;
								}
								
								dp[newx][newy][k][l] = (dp[newx][newy][k][l] + dp[i][j][k][l]) % mod;
							}
						}
					}
				}
			}
		}
	}
	
	for (int i = 1 ; i <= m ; i ++) {
		for (int j = 1 ; j <= m ; j ++) {
			ans = (ans + dp[n][i][n][j]) % mod; // 第一只青蛙在 i ,第二只青蛙在 j 的方案数
		}
	}
	
	printf("%d\n" , ans);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值