【花样枚举】bzoj 1177 apio2009采油区域

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1177
题目大意:给你一个矩阵,让你选出三个边长为k且互不重叠的正方形使得所有正方形内的数的和最大

这题正解还真是枚举……不过这个枚举很厉害的说
最暴力就是枚举三个正方形的位置,不过一定不会AC……
但是多思考一下会发现一件事情:任意两条直线都可以把矩阵分为三部分,且可以做到将三个正方形分别分到三部分里
1.可以通过两条互相垂直的线分成三块,如下图

这里写图片描述 这里写图片描述

这里写图片描述 这里写图片描述

2.可以通过两条平行的平行的线分成三块,如下图

这里写图片描述 这里写图片描述

知道了这个可以做什么呢?反正正方形一定在被分出来的每一部分里对吧……所以我们可以通过维护每一部分的正方形的最大值来快速寻找每个部分里的正方形
枚举两条线,然后通过 二维前缀和+dp 或者 二维前缀和+萝比特酱(lowbit) 维护下最大值就可以了,dp的维护方法就是xx[i][j]代表以(i,j)为右下角,(1,1)为左上角的区域内的最大值,然后再开三个类似的数组……

贴代码(dp)

#include <cstdio>
#include <cstring>
#include <iostream>
#define INF 2147483647
#define MAXN 2000
using namespace std;
int a[MAXN][MAXN];
int s[MAXN][MAXN];
int xx[MAXN][MAXN];
int xy[MAXN][MAXN];
int yx[MAXN][MAXN];
int yy[MAXN][MAXN];
int lx[MAXN];
int ly[MAXN];
int n,m,k;
int ans = 0;
int p,q;
int o;

int read()
{
    int x = 0;
    char w;
    w = getchar();
    while(w >= '0' && w <= '9')
    {
        x *= 10;
        x += w - '0';
        w = getchar();
    }
    return x;
}

int nico(int x,int y)
{
    return s[x][y-1] + s[x-1][y] - s[x-1][y-1] + a[x][y];
}

int poi(int x,int y)
{
    if(x < k || y < k)
        return 0;
    return s[x-k][y-k] - s[x][y-k] - s[x-k][y] + s[x][y];
}

void dp()
{
    for(int i = k;i <= o;i ++)
    {
        for(int j = k;j <= o;j ++)
        {
            xx[i][j] = max(xx[i-1][j],xx[i][j-1]);
            xx[i][j] = max(xx[i][j],poi(i,j));
        }
    }
    for(int i = k;i <= o;i ++)
    {
        for(int j = o;j >= k;j --)
        {
            xy[i][j] = max(xy[i-1][j],xy[i][j+1]);
            xy[i][j] = max(xy[i][j],poi(i,j));
        }
    }
    for(int i = o;i >= k;i --)
    {
        for(int j = k;j <= o;j ++)
        {
            yx[i][j] = max(yx[i+1][j],yx[i][j-1]);
            yx[i][j] = max(yx[i][j],poi(i,j));
        }
    }
    for(int i = o;i >= k;i --)
    {
        for(int j = o;j >= k;j --)
        {
            yy[i][j] = max(yy[i+1][j],yy[i][j+1]);
            yy[i][j] = max(yy[i][j],poi(i,j));
        }
    }
    for(int i = 1;i <= o;i ++)
    {
        for(int j = 1;j <= o;j ++)
        {
            lx[i] = max(lx[i],poi(i,j) );
            ly[i] = max(ly[i],poi(j,i) );
        }
    }
    return ;
}
void sol()
{
    int minn;
    for(int i = k;i <= o;i ++)
    {
        for(int j = k;j <= o;j ++)
        {
            ans = max(ans,xx[i][j] + xy[i][j+k] + yx[i+k][m]);
            ans = max(ans,xx[i][j] + yx[i+k][j] + xy[n][j+k]);
            ans = max(ans,xy[i][j+k] + yy[i+k][j+k] + xx[n][j]);
            ans = max(ans,yx[i+k][j] + yy[i+k][j+k] + xx[i][m]);
        }
    }
    for(int i = k;i <= n;i ++)
        for(int j = i+k;j <= n-k;j ++)
            ans = max(ans,xx[i][m] + lx[j] + yx[j+k][m]);
    for(int i = k;i <= m;i ++)
        for(int j = i+k;j <= m-k;j ++)
            ans = max(ans,xx[n][i] + ly[j] + xy[n][j+k]);
    return ;
}

int main()
{
    n = read();
    m = read();
    k = read();
    o = max(n,m);
    for(int i = 1;i <= n;i ++)
        for(int j = 1;j <= m;j ++)
            a[i][j] = read();
            s[i][j] = nico(i,j);
    dp();
    sol();
    cout << ans;
    return 0;
}

/*
6 6 2
1 0 1 0 1 2
0 1 0 2 2 1
1 2 0 2 2 0
0 1 1 0 2 2
2 2 1 2 1 1
0 2 0 0 1 2

*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值