bzoj 2241: [SDOI2011]打地鼠

Posted ck666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bzoj 2241: [SDOI2011]打地鼠相关的知识,希望对你有一定的参考价值。

2241: [SDOI2011]打地鼠

2017-08-26


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

【样例说明】
使用2*2的锤子,分别在左上、左下、右上、右下挥舞一次。
【数据规模和约定】
对于100%的数据,1<=M,N<=100,其他数据不小于0,不大于10^5

暴力模拟+剪枝
从大到小开始模拟锤子的大小,然后判断是否可以用这么大的锤子;
优化1:如果锤子面积不能被地鼠总数整除,这个锤子就一定不是合法锤子;
优化2:如果当前答案比(地鼠总数/锤子面积)小当前一定不是最优答案,可以删去;
技术分享
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstdlib>
using namespace std;
int m,n,sum;
int a[109][109];
int ans;int lin[109][109];
bool pd(int c,int k){
    int re=0;
    for(int e=1;e<=n;e++)
        for(int r=1;r<=m;r++)lin[e][r]=a[e][r];
    for(int e=1;e+c-1<=n;e++)
        for(int r=1;r+k-1<=m;r++)
            for(int q=e+c-1;q>=e;q--)
                for(int w=r+k-1;w>=r;w--)
        {    
            re+=lin[e][r];
            lin[q][w]-=lin[e][r];
            if(lin[q][w]<0)return 0;
        }
    if(sum==re)return 1;
    return 0;
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){cin>>a[i][j];sum+=a[i][j];}
    ans=sum;
    for(int i=n;i>=1;i--)
        for(int j=m;j>=1;j--)
    {    
        if(sum==(sum/i/j)*i*j)
        if(ans>(sum/i/j))
        if(pd(i,j))   ans=min(sum/i/j,ans);
    }
    cout<<ans;
    return 0;
}
2241_cpp

by:s_a_b_e_r


暴力啊
当然暴力啊
暴力出奇迹啊
n^2枚举锤子大小
n^2枚举锤子砸的左上角
n^2砸掉范围内的地鼠
因为目标是把所有地鼠砸掉所以每锤砸掉的个数=左上角个数
砸着砸着发现哪个格的地鼠数变负数了
说明这个锤子不合法
回去枚举下一个锤子
共计n^6
实力剪枝卡过去……
具体优化楼上已经说了我就不说了(懒
嗯你说正解是差分?
……
暴力大法好(逃
附代码
技术分享
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=105;
int m,n,sum;
int a[N][N],b[N][N];
int check(int r,int c)
{
    int res=0;
    for(int i=1;i<=m-r+1;++i)
    for(int j=1;j<=n-c+1;++j)
    {
      if(b[i][j]<0)return sum;
      res+=b[i][j];
      for(int x=0;x<r;++x)
      for(int y=0;y<c;++y)
        if(x||y)b[i+x][j+y]-=b[i][j];
      b[i][j]=0;
    }
    for(int i=1;i<=m;++i)
    for(int j=1;j<=n;++j)
    if(b[i][j])return sum;
    return res;
}
int main()
{
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;++i)
    for(int j=1;j<=n;++j)
    {scanf("%d",&a[i][j]);sum+=a[i][j];}
    int ans=sum;
    for(int r=1;r<=m;++r)
    for(int c=1;c<=n;++c)
    if(sum%(r*c)==0&&ans>sum/(r*c))
    {
      for(int i=1;i<=m;++i)
      for(int j=1;j<=n;++j)
        b[i][j]=a[i][j];
      ans=min(ans,check(r,c));
    }
    cout<<ans<<endl;
    return 0;
}
2241(wypx)

s:哇,w抽出sr了,不行我还是去玩神代吧.....

w:小姐姐真好看……不对快去做题啊>_<










以上是关于bzoj 2241: [SDOI2011]打地鼠的主要内容,如果未能解决你的问题,请参考以下文章

bzoj 2241 [SDOI2011]打地鼠

BZOJ-2241: [SDOI2011]打地鼠 (模拟+枚举)

bzoj 2241: [SDOI2011]打地鼠

bzoj2241:[SDOI2011]打地鼠

BZOJ 2241([SDOI2011]打地鼠-二分判断+贪心)

bzoj2241: [SDOI2011]打地鼠