[洛谷]P1736 创意吃鱼法 (#记忆化搜索)

本文探讨了一种基于01矩阵的鱼池策略问题,猫猫的目标是在池中找到最大的正方形子矩阵,其对角线上有连续的鱼,其余位置无鱼,以最大化一口吞食的鱼的数量。通过使用记忆化搜索和二维前缀和的方法,解决了如何高效地找出这样的子矩阵。

题目背景

感谢@throusea 贡献的两组数据

题目描述

回到家中的猫猫把三桶鱼全部转移到了她那长方形大池子中,然后开始思考:到底要以何种方法吃鱼呢(猫猫就是这么可爱,吃鱼也要想好吃法 ^_*)。她发现,把大池子视为01矩阵(0表示对应位置无鱼,1表示对应位置有鱼)有助于决定吃鱼策略。

在代表池子的01矩阵中,有很多的正方形子矩阵,如果某个正方形子矩阵的某条对角线上都有鱼,且此正方形子矩阵的其他地方无鱼,猫猫就可以从这个正方形子矩阵“对角线的一端”下口,只一吸,就能把对角线上的那一队鲜鱼吸入口中。

猫猫是个贪婪的家伙,所以她想一口吃掉尽量多的鱼。请你帮猫猫计算一下,她一口下去,最多可以吃掉多少条鱼?

输入格式

有多组输入数据,每组数据:

第一行有两个整数n和m(n,m≥1),描述池塘规模。接下来的n行,每行有m个数字(非“0”即“1”)。每两个数字之间用空格隔开。

对于30%的数据,有n,m≤100

对于60%的数据,有n,m≤1000

对于100%的数据,有n,m≤2500

输出格式

只有一个整数——猫猫一口下去可以吃掉的鱼的数量,占一行,行末有回车。

输入输出样例

输入 #1复制

4 6
0 1 0 1 0 0
0 0 1 0 1 0
1 1 0 0 0 1
0 1 1 0 1 0

输出 #1复制

3

说明/提示

右上角的

1 0 0
0 1 0
0 0 1

思路

拿到这题立马想到了最大正方形那题,可是却不会推方程。写了个记忆化搜索。

怎么去搜?对于任意一个子正方形,要满足鱼都在对角线,也就是对角线的数字不为0;子正方形其他地方没有鱼,也就是对角线以外都是0。假如(x,y)有鱼,那就可以dfs(x+1,y+1)和dfs(x+1,y-1)来判断对角线上是否有鱼。既然一个子正方形只有对角线有鱼,那么该正方形的数字和就是对角线的长度。所以利用二维前缀和把整个矩形总和算出来。

#include <stdio.h>
#include <iostream>
#define N 2501
using namespace std;
int a[N][N],dp[N][N][2],sum[N][N],n,m,s,orgx,orgy;
int tox[5]={0,1,0,-1,0},toy[5]={0,0,1,0,-1};
int dfs1(int x,int y)
{
	if(x>n || y>m ) return 0;
    //if(dp[x][y][0]) return dp[x][y][0];//为什么注释?下面有解释 
	register int cnt(sum[x][y]-sum[orgx-1][y]-sum[x][orgy-1]+sum[orgx-1][orgy-1]);
	if(cnt!=x-orgx+1 || a[x][y]==0)
	{
		return 0;
	}
	dp[x][y][0]=dfs1(x+1,y+1)+1;
	if(dp[x][y][0]) return dp[x][y][0];//为什么到这里才进行记忆化?下面有解释 
}
int dfs2(int x,int y)
{
	if(x>n || y<1 ) return 0;
	register int cnt(sum[x][orgy]-sum[orgx-1][orgy]-sum[x][y-1]+sum[orgx-1][y-1]);
	if(cnt!=x-orgx+1 || a[x][y]==0)
	{
		return 0;
	}
	dp[x][y][1]=dfs2(x+1,y-1)+1;
	if(dp[x][y][1]) return dp[x][y][1];
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	register int i,j,k;
	cin>>n>>m;
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=m;j++)
		{
			cin>>a[i][j];
			sum[i][j]=sum[i][j]+sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+a[i][j];
		}
	}
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=m;j++)
		{
			orgx=i;
			orgy=j;
			dp[i][j][0]=dfs1(i,j);
		}
	}
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=m;j++)
		{
			orgx=i;
			orgy=j;
			dp[i][j][1]=dfs2(i,j);
		}
	}
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=m;j++)
		{
			s=max(s,max(dp[i][j][0],dp[i][j][1]));
		}
	}
	cout<<s<<endl;
	return 0;
}

接下来来解答代码注释中的问题。

假设有如下4*4的矩形:

1 0 1 0
0 1 0 0
1 0 1 0
0 0 0 1

答案是3,但如果把注释那句话放到前面,答案会就变成2。因为dp[2][2][0]会先复制成2,再次枚举到该点时会直接返回,而得不到右下角的正确矩形。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值