【SDOI 2011】打地鼠

本文介绍了一种针对经典打地鼠游戏的优化算法。通过对游戏规则的改造,增加锤子覆盖范围,实现一次性打击更多地鼠的目标。文章详细解释了如何通过调整锤子规格以最小化击打次数,并提供了完整的算法思路及实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

打地鼠是这样的一个游戏:地面上有一些地鼠洞,地鼠们会不时从洞里探出头来很短时间后又缩回洞中。玩家的目标是在地鼠伸出头时,用锤子砸其头部,砸到的地鼠越多分数也就越高。 

游戏中的锤子每次只能打一只地鼠,如果多只地鼠同时探出头,玩家只能通过多次挥舞锤子的方式打掉所有的地鼠。你认为这锤子太没用了,所以你改装了锤子,增加了锤子与地面的接触面积,使其每次可以击打一片区域。如果我们把地面看做m*n的方阵,其每个元素都代表一个地鼠洞,那么锤子可以覆盖R*C区域内的所有地鼠洞。但是改装后的锤子有一个缺点:每次挥舞锤子时,对于这的R*C区域中的所有地洞,锤子会打掉恰好一只地鼠。也就是说锤子覆盖的区域中,每个地洞必须至少有1只地鼠,且如果某个地洞中地鼠的个数大于1,那么这个地洞只会有1只地鼠被打掉,因此每次挥舞锤子时,恰好有R*C只地鼠被打掉。由于锤子的内部结构过于精密,因此在游戏过程中你不能旋转锤子(即不能互换R和C)。 

你可以任意更改锤子的规格(即你可以任意规定R和C的大小),但是改装锤子的工作只能在打地鼠前进行(即你不可以打掉一部分地鼠后,再改变锤子的规格)。你的任务是求出要想打掉所有的地鼠,至少需要挥舞锤子的次数。 
Hint:由于你可以把锤子的大小设置为1*1,因此本题总是有解的。

Input

第一行包含两个正整数m和n; 
下面m行每n行个正整数描述地图,每个数字表示相应位置的地洞中地鼠的数量。

Output

输出一个整数,表示最少的挥舞次数。

Sample Input

3 3
1 2 1
2 4 2
1 2 1

Sample Output

4

Hint

【样例说明】 
使用2*2的锤子,分别在左上、左下、右上、右下挥舞一次。 
对于30%的数据,m,n<=5; 
对于60%的数据,m,n<=30; 
对于100%的数据,1<=m,n<=100,其他数据不小于0,不大于10^5。


【分析】

        可以分析得出,答案是具有单调性的。锤子的面积越大,则答案越小。那么我们只需生成所有可能的锤子面积,排序后从大到小枚举验证。第一个满足条件则输出

        优化:sum表示所有点的权值和,我们只验证可以整除sum的锤子面积。答案为 sum/s, s表示面积。

        验证:通过贪心验证,对于当前点,若不剩老鼠了则讨论下一个点。若剩,则将以它为左上角的矩形权值都减少它的权值。在验证时,如果以它为左上角的矩形超出了边界,return false。


【代码】

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<ctime>
#include<iostream>
#include<algorithm>
using namespace std;
struct node{
	int x,y,s;
}Num[20005];
int N,M,sum,top;
int Map[105][105],G[105][105];
void _init()
{
	scanf("%d%d",&N,&M);
	for(int i=1;i<=N;i++)
		for(int j=1;j<=M;j++)
		{
			scanf("%d",&Map[i][j]);
			sum+=Map[i][j];
		}
}
bool _cmp(node A,node B)
{
	return A.s>B.s;
}
void _next(int &x,int &y)
{
	if(y<M)
		y++;
	else
	{
		x++;
		y=1;
	}
}
bool _check(int R,int C)
{
	int t,x,y;
	memcpy(G,Map,sizeof(Map));
	x=y=1;
	while(x<=N&&y<=M)
	{
		if(G[x][y]==0)
		{
			_next(x,y);   //下一个点
			continue;
		}
		t=G[x][y];
		if(x+R-1>N) return false;     //矩形超出边界
		if(y+C-1>M) return false;
		for(int i=x;i<=x+R-1;i++)    //减少权值
			for(int j=y;j<=y+C-1;j++) 
			{
				if(G[i][j]>=t)
				    G[i][j]-=t;
				else
					return false;
			}
	}
	return true;
}
void _solve()
{
	for(int i=1;i<=N;i++)        //生成所有可能
		for(int j=1;j<=M;j++)
		{
			Num[++top].x=i;
			Num[top].y=j;
			Num[top].s=i*j;
		}
	sort(Num+1,Num+top+1,_cmp);   //排序
	for(int i=1;i<=top;i++)
	    if(sum%Num[i].s==0)      //只验证能整除的
		    if(_check(Num[i].x,Num[i].y))  
			{
				printf("%d\n",sum/Num[i].s);
				return;
			}
}
int main()
{
	_init();
	_solve();
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值