计蒜客 蒜头君打地鼠 (矩阵旋转 + 二维前缀和)


链接 : Here!

思路 :

  1. 首先看数据范围 $1\leq n \leq 2000$, $1 \leq k \leq 100$ , 直接暴力肯定 $T$, 如果锤子是正着的就好办了, 就可用二维前缀和的技巧来进行降维了!
  2. 所以直接将矩阵右旋45°, 让锤子正过来, 右旋时需要注意原坐标 $(x, y)$ 被映射为 $(x+y, n-1-x-y)$ 了, 原矩阵被放大为$(2n-1) * (2n-1)$ 了, 同时锤子也被放大为 $(2k-1) * (2k-1)$
  3. 直接扫描一遍二维数组, 计算一下二维前缀和, 然后枚举锤子的四个角, $O(1)$ 的时间内便能找到锤子所在范围中地鼠的个数了

注意 : 这个数据好像有毒, 按道理来说锤子不一定全部落在原矩形内砸到的地鼠最多, 很有可能原矩形只有一条边上有地鼠, 因此如果想得到最大地鼠数的话, 锤子的一部分必须在外面, 但是我修改 $check$ 数组后反而通不过所有数据 QAQ


代码 :

/*************************************************************************
	> File Name: 蒜头君打地鼠.cpp
	> Author: 
	> Mail: 
	> Created Time: 2017年11月20日 星期一 19时58分35秒
 ************************************************************************/

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;

#define MAX_N 4000

int a[MAX_N][MAX_N];
int sum[MAX_N][MAX_N];
bool vis[MAX_N][MAX_N];
int n, k, t_n;

// 这个题有毒吧...
// 注意旋转后矩阵和锤子都变大了
// 这里旋转后的点并不是严格遵循数学中的旋转
void read() {
    memset(a, 0, sizeof(a));
    memset(sum, 0, sizeof(sum));
    memset(vis, 0, sizeof(vis));
    scanf("%d%d", &n, &k);
    for (int i = 0 ; i < n ; ++i) {
        for (int j = 0 ; j < n ; ++j) {
            int temp;
            scanf("%d", &temp);
            a[i + j][n - 1 - i + j] = temp;
            vis[i + j][n - 1 - i + j] = 1;
        }
    }
    t_n = n;
    n = n * 2 - 1;
    k = k * 2 - 1;
    // 可以清晰的查看到矩阵被放大了
    // 旋转后矩阵的大小变为了原来的 (2 * n - 1) X (2 * n - 1)
    // 当然锤子也应该变大
    // 看起来是右旋45度
    // 
    // TODO 拓展问题 : 如果将矩阵左旋45度,旋转90度,旋转180度...等等
}

void cal_sum() {
    for (int i = 0 ; i < n ; ++i) {
        for (int j = 0 ; j < n ; ++j) {
            if (i > 0) sum[i][j] += sum[i - 1][j];
            if (j > 0) sum[i][j] += sum[i][j - 1];
            if (i > 0 && j > 0) sum[i][j] -= sum[i - 1][j - 1];
            sum[i][j] += a[i][j];
        }
    }
}

// 检查(x, y)是否合法
bool check(int x, int y) {
    return (y <= x + t_n - 1) && (y >= x - t_n + 1) && (y >= -x + t_n - 1) && (y <= -x + (3 * t_n - 3));
}

int cal_max() {
    int max_value = -1;
    for (int i = 0 ; i < n && i + k - 1 < n ; ++i) {
        for (int j = 0 ; j < n && j + k - 1 < n ; ++j) {
            if (!(check(i, j) && check(i + k - 1, j) && check(i, j + k - 1) && check(i + k - 1, j + k - 1))) continue;
            // 锤子一定要在原来的矩形内
            if (!vis[i][j]) continue;
            int now = sum[i + k - 1][j + k - 1];
            if (i > 0) now -= sum[i - 1][j + k - 1];
            if (j > 0) now -= sum[i + k - 1][j - 1];
            if (i > 0 && j > 0) now += sum[i - 1][j - 1];
            max_value = max(max_value, now);
        }
    }
    return max_value;
}

void solve() {
    cal_sum();
    printf("%d\n", cal_max());
}

int main() {
    read();
    solve();
    return 0;
}

posted @ 2017-11-23 13:51  ojnQ  阅读(358)  评论(0编辑  收藏  举报