池塘

博客围绕青蛙过河问题展开,将池塘看作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);
}

<think>我们被要求分析用户提供的“池塘计数”代码出现WA的原因。代码的目标是计算一个二维网格中池塘的数量(由字符'W'表示水域,'.'表示陆地)。代码使用BFS遍历相邻的池塘单元格。 首先,我们需要检查代码中可能存在的错误点: 1. 输入处理:用户输入字符,如果字符是 '.',则将 vis[i][j] 设为 true(表示陆地,已访问);否则(即'W'),vis[i][j] 初始为 false(表示未访问的水域)。注意,代码中初始化 vis 数组为全局变量(默认初始化为0,即false)。然后,在输入时,如果字符是 '.',则设置 vis[i][j]=1(即陆地),否则保持0(即水域,后续需要遍历)。 2. BFS遍历:从每个未访问的点(即水域点)开始BFS,将相邻的8个方向(包括对角线)的未访问水域点标记为已访问,并计入一个池塘。 3. 方向数组:注意方向数组 dx 和 dy 的定义。用户代码中: int dx[8]={1,-1,0,0,1,1,-1-1}; // 注意:这里有一个错误,-1-1 应该是 -1, -1,但写成了 -1-1(即-2) int dy[8]={0,0,1,-1,1,-1,1,-1}; // 这里也有问题,因为dx有8个元素,但第7个元素写成了-1-1(即-2),而第8个元素缺失。 方向数组的错误可能是导致WA的主要原因。因为当i=6时,dx[6] = -1-1 = -2,而dy[6]=1;i=7时,dx[7]没有定义(实际上编译器可能会将后面一个数作为第7个元素,但这里写法错误,导致后面元素个数不足)。 具体看: dx[8] = {1, -1, 0, 0, 1, 1, -1-1}; 这里,-1-1是一个表达式,等于-2,所以dx数组实际上是:{1,-1,0,0,1,1,-2},然后后面还缺一个元素,但数组大小为8,所以最后一个元素可能是随机值(或者0,取决于编译器)。同样,dy数组:{0,0,1,-1,1,-1,1,-1},这个没有问题,有8个元素? 但是,dy数组定义的是8个元素,而dx数组由于第7个元素写成了-1-1(即-2),那么实际初始化的元素个数为7个(因为最后一个元素没有显式给出,但数组大小是8,那么最后一个元素会是0?)实际上,C++中数组初始化如果给定的初始值个数小于数组大小,则剩余元素被初始化为0。但是这里,由于第6个元素是1,第7个元素是-2,第8个元素没有给出,所以为0。所以dx数组实际为: dx = {1, -1, 0, 0, 1, 1, -2, 0} 而dy数组完整。 这会导致方向偏移错误:比如第6个方向应该是(-1,1)变成了(-2,1),第7个方向应该是(-1,-1)变成了(0,-1)(因为dy[7]是-1,但dx[7]是0),这显然不对。 正确的8方向应该是: dx = {1, -1, 0, 0, 1, -1, 1, -1} dy = {0, 0, 1, -1, 1, 1, -1, -1} 或者更常见的写法: dx = {1,1,0,-1,-1,-1,0,1} dy = {0,1,1,1,0,-1,-1,-1} 但用户代码中的dx明显有误。 4. 其他可能错误: - 边界判断:r和c的范围是从1到n(行)和1到m(列),边界判断正确。 - 访问标记:在BFS中,将访问过的点标记,避免重复访问,正确。 - 计数:对每个未访问的水域点进行BFS并计数,逻辑正确。 因此,主要问题在方向数组。 另外,代码中有一个小问题:在BFS中,当遇到一个相邻点(r,c)时,检查是否越界,然后检查是否未访问(!vis[r][c]),然后将它标记为已访问并入队。这里注意,初始时,只有陆地被标记为已访问,水域未访问。但是在BFS中,我们会将水域标记为已访问,这样就不会重复访问。逻辑正确。 但是,注意题目中池塘是由字符'W'表示的,而代码中并没有显式地判断字符是'W'还是'.',而是通过vis数组初始设置来区分的:在输入时,如果字符是 '.',则设置vis[i][j]=1(陆地),否则(应该是'W')保持0(未访问)。那么,在后续遍历时,我们只对vis[i][j]为0的点(即水域)进行BFS。所以这里要求输入中除了 '.' 就是 'W'。如果输入有其他字符,可能会出错。但题目一般保证输入只有这两种字符。 所以,修改方向数组即可。 此外,还有一个问题:代码中使用了#define int long long,但本题数据范围一般不需要long long,不过这不是WA的原因。 修改方向数组为正确的8方向: 例如,可以修改为: int dx[8] = {1, -1, 0, 0, 1, -1, 1, -1}; int dy[8] = {0, 0, 1, -1, 1, 1, -1, -1}; 但是,这样写实际上只包含了8个方向中的8个,没有问题。不过注意,上面的dx[5]和dx[7]都是-1,dy[5]是1,dy[7]是-1,所以包含了: (1,0), (-1,0), (0,1), (0,-1), (1,1), (-1,1), (1,-1), (-1,-1) 所以正确。 另外,用户代码中dx数组的第7个元素写成了“-1-1”,这实际上是两个负数的运算,等于-2,所以是错误的。 因此,我们只需修正方向数组即可。 但是,我们也要注意,在BFS中,我们入队了周围的点,但是我们在入队前检查了该点是否为未访问(即!vis[r][c]),并且将其标记为已访问。但是,如果这个点是陆地(即原本vis[r][c]为1),那么我们不会将其加入队列,这是正确的,因为陆地不应该被计入池塘。 所以,总结:方向数组的错误导致BFS遍历邻居时方向不正确,从而可能漏掉一些相连的水域或者错误地遍历到不相连的水域,导致计数错误。 修改方向数组后,应该可以AC。 但是,我们还需要注意:题目中池塘的定义是相连的一片水域(八方向相连)。所以用8方向BFS正确。 建议:将方向数组改为: int dx[8] = {1, -1, 0, 0, 1, 1, -1, -1}; int dy[8] = {0, 0, 1, -1, 1, -1, 1, -1}; 但是注意,上面这样写,dx[6]和dx[7]分别是-1和-1,而dy[6]和dy[7]分别是1和-1,所以实际上包含了: (1,0), (-1,0), (0,1), (0,-1), (1,1), (1,-1), (-1,1), (-1,-1) 所以也是正确的8方向。 原代码错误在于第6个方向dx写成了1,但应该是1(对应右下)和-1(对应右上)?实际上原代码的dx[4]和dx[5]都是1(对应右下和右上?),但dy[4]和dy[5]分别是1和-1,所以右下是(1,1),左下是(1,-1)?然后dx[6]和dx[7]应该是-1开头的方向,但写错了。 因此,我们修改dx和dy数组的定义。 修改后的代码: 将 int dx[8]={1,-1,0,0,1,1,-1-1}; int dy[8]={0,0,1,-1,1,-1,1,-1}; 改为: int dx[8] = {1, -1, 0, 0, 1, 1, -1, -1}; // 注意:这里第7个和第8个分别是-1和-1 int dy[8] = {0, 0, 1, -1, 1, -1, 1, -1}; // 保持不变(但注意dy[6]和dy[7]应该是1和-1?) 但是这样修改后,方向对应: 0: (1,0) 下 1: (-1,0) 上 2: (0,1) 右 3: (0,-1) 左 4: (1,1) 右下 5: (1,-1) 左下 6: (-1,1) 右上 7: (-1,-1) 左上 这样8方向就完整了。 所以,修改方向数组即可。 但是,我们还要注意:原代码中dy数组的第6个元素(下标为6)和第7个元素(下标为7)分别是1和-1,而dx数组的第6、7个分别是-1和-1,所以第6个方向是(-1,1)(右上),第7个方向是(-1,-1)(左上)。正确。 因此,WA的原因是方向数组初始化错误,导致BFS遍历方向错误,从而漏掉或错误连接池塘区域。 修正方向数组,应该就能通过。 另外,我们注意到用户代码中,在输入时用了: fors(i,1,n) { fors(j,1,m) { char c; cin>>c; if(c=='.'){ vis[i][j]=1; } } } 这里,fors(i,1,n) 表示从1到n,fors(j,1,m) 表示从1到m,所以数组下标从1开始,符合边界判断(r>=1 && r<=n, c>=1 && c<=m)。 所以,除了方向数组,其他逻辑没有问题。 结论:方向数组错误是导致WA的主要原因。 建议修改方向数组为: int dx[8] = {1, -1, 0, 0, 1, 1, -1, -1}; int dy[8] = {0, 0, 1, -1, 1, -1, 1, -1}; 注意:这里dy[4]是1,dy[5]是-1,dy[6]是1,dy[7]是-1。这样组合得到: 4: (1,1) -> 右下 5: (1,-1) -> 左下 6: (-1,1) -> 右上 7: (-1,-1) -> 左上 完全正确。 因此,将这两行修改后,重新提交即可。 如果还有其他问题,可能是输入数据量的问题,但题目中N=1e3+5,最大网格1000x1000,BFS每个点最多访问一次,复杂度O(n*m),可以接受。 下面给出修改后的代码(仅修改方向数组): </think>根据提供的池塘计数代码,出现WA(答案错误)的主要原因在于**方向数组定义错误**,导致BFS遍历相邻单元格时出现偏移。以下是详细分析: ### 错误原因分析 1. **方向数组初始化错误**(关键问题): ```cpp int dx[8]={1,-1,0,0,1,1,-1-1}; // 错误写法 int dy[8]={0,0,1,-1,1,-1,1,-1}; ``` - `dx[6] = -1-1` 被编译器解释为 `-2`(数学运算),而非预期的 `-1`; - `dx` 数组实际值为 `{1,-1,0,0,1,1,-2,0}`(最后一个元素因未初始化补0); - 导致相邻单元格坐标计算错误(如 `(x-2,y+1)` 偏移异常)。 2. **BFS遍历结果错误**: - 错误的方向数组使BFS无法正确遍历8邻域(对角线方向偏移异常); - 部分相连的池塘被错误分割,或非相连池塘被错误合并; - 最终池塘计数结果偏离预期。 ### 修正方案 1. **更正方向数组**: ```cpp int dx[8] = {1, -1, 0, 0, 1, 1, -1, -1}; // 修正:显式分隔-1 int dy[8] = {0, 0, 1, -1, 1, -1, 1, -1}; // 保持原正确部分 ``` - 确保8个方向正确对应: `→ ← ↑ ↓ ↘ ↙ ↗ ↖` 2. **修正后代码关键部分**: ```cpp // 修正方向数组 int dx[8] = {1, -1, 0, 0, 1, 1, -1, -1}; int dy[8] = {0, 0, 1, -1, 1, -1, 1, -1}; void bfs(int x, int y) { vis[x][y] = 1; queue<pair<int, int>> q; q.push({x, y}); while (!q.empty()) { auto [u, v] = q.front(); // C++17结构化绑定更简洁 q.pop(); for (int i = 0; i < 8; ++i) { int r = u + dx[i], c = v + dy[i]; if (r < 1 || c < 1 || r > n || c > m) continue; if (!vis[r][c]) { vis[r][c] = 1; q.push({r, c}); } } } } ``` ### 其他潜在问题排查 1. **输入格式兼容性**: - 代码假设输入仅含 `'.'` 和 `'W'`,若含空格或换行符需改用 `cin>>ws` 或调整读取逻辑。 2. **越界访问风险**: - 全局数组 `vis[N][N]` 尺寸为 $10^3+5$,需确保 $n, m \leq 1000$; - 若测试数据 $n, m > 1000$ 会导致RE(Runtime Error)。 3. **性能优化**: - 当网格较大时(如 $10^3 \times 10^3$),BFS每次清空队列即可,无需额外优化。 ### 验证建议 1. 使用以下测试数据校验: ``` 3 3 .W. W.W .W. ``` **正确输出**:$4$(孤立池塘) **原错误输出**:因方向错误可能输出 $1$ 或 $2$。 2. 提交至在线判题系统(如Codeforces、LeetCode)验证修正结果。 ### 相关问题 1. BFS中方向数组的定义有哪些常见注意事项? 2. 如何设计测试用例覆盖8邻域遍历的边界情况? 3. 深度优先搜索(DFS)实现池塘计数时代码需如何修改? > 修正方向数组可解决核心逻辑错误,其余部分代码逻辑正确[^1][^3]。建议测试时覆盖全邻域边界用例[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值