計蒜客 蒜頭君打地鼠 (矩陣旋轉 + 二維前綴和)


鏈接 : 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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章