Codeforces Round #841 (Div. 2) and Divide by Zero 2022 D. Valiant‘s New Map


题目链接https://codeforces.com/contest/1731/problem/D

题意:

给你你个nm的矩阵, a [ i ] [ j ] a[i][j] a[i][j]代表这个方格的高度,需要找出最大的l,满足至少有一个 l ∗ l l*l ll的子矩阵的每一个元素大于等于 l l l即子矩阵的最小值要大于等于 l l l

题目满足条件 ∑ n m > = 1 0 6 \sum nm>=10^6 nm>=106

题解:

从nm的范围可推断,复杂度大致为 n m l o g nmlog nmlog。显然,我们可以二分最后的正方形的边长,在O(nm)复杂度内判断是否存在满足条件的正方形,那么我们就必须要在O(1)的复杂度内判断一个正方形的最小值是否大于等于边长。

还有一个要注意的点是,本题是保证nm<=1e6,不能像往常一样在全局定义数组大小,而要在函数内部动态定义

方法一:二维ST表

说实话,在这之前了解过ST表,没听说过二维的,,,赛中一直在一维ST

二维ST表跟一维差别应该也只是在维度上变成了二维的,本质原理是一样的

定义 s t [ i ] [ j ] [ k ] st[i][j][k] st[i][j][k]:以(i,j)为左上角,2^k为边长的矩阵的最小值(这里需要的是最小值)
预处理

for (int k = 1; k <= Log2[min(n,m]; k++) { // 依题而定
    for (int i = 1; i + (1 << (k - 1)) <= n + 1; i++) {
        for (int j = 1; j + (1 << (k - 1)) <= m + 1; j++) {
            st[i][j][k] = min({st[i][j][k - 1], 
                               st[i + (1 << (k - 1))][j][k - 1], 
                               st[i][j + (1 << (k - 1))][k - 1], 
                               st[i + (1 << (k - 1))][j + (1 << (k - 1))][k - 1]});
        }
    }
}

查询
以(i,j)为左上角,x为边长的矩形的最值

int quer(int i, int j, int x) {
    int k = Log2[x];
    return min({st[i][j][k], 
                st[i + x - (1 << k)][j][k], 
                st[i][j + x - (1 << k)][k], 
                st[i + x - (1 << k)][j + x - (1 << k)][k]});
}

参考代码

#include <bits/stdc++.h>
using namespace std;
// #define int long long
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
#define pii pair<int, int>

int n, m;
int Log2[N];
void solve() {
    cin >> n >> m;
    int a[n + 2][m + 2];
    int st[n + 2][m + 2][11];
    memset(st, 127, sizeof st);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> a[i][j], st[i][j][0] = a[i][j];
    //预处理
    for (int k = 1; k <= Log2[min(n, m)]; k++) {
        for (int i = 1; i + (1 << (k - 1)) <= n + 1; i++) {
            for (int j = 1; j + (1 << (k - 1)) <= m + 1; j++) {
                st[i][j][k] = min({st[i][j][k - 1], st[i + (1 << (k - 1))][j][k - 1], st[i][j + (1 << (k - 1))][k - 1], st[i + (1 << (k - 1))][j + (1 << (k - 1))][k - 1]});
            }
        }
    }
    int ans = 0, l = 1, r = min(n, m);
    while (l <= r) {
        int mid = (l + r) >> 1;
        bool ok = false;
        int k = Log2[mid];
        for (int i = 1; i + mid <= n + 1; i++) {
            for (int j = 1; j + mid <= m + 1; j++) {
                if (min({st[i][j][k], st[i + mid - (1 << k)][j][k], st[i][j + mid - (1 << k)][k], st[i + mid - (1 << k)][j + mid - (1 << k)][k]}) >= mid) {
                    ok = true;
                    break;
                }
            }
        }
        if (ok)
            l = mid + 1, ans = mid;
        else
            r = mid - 1;
    }
    cout << ans << "\n";
}
signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    for (int i = 2; i < N; i++)  // 预处理
        Log2[i] = Log2[i >> 1] + 1;
    int T = 1;
    cin >> T;
    while (T--)
        solve();
}

方法二:二维前缀和

在二分边长去check的时候边长可以看作确定了,那么我们可以构造一个矩阵,当 a [ i ] [ j ] a[i][j] a[i][j]大于等于边长为1,否则为0。那么在判断一个以 ( i , j ) (i,j) (i,j)为左上角,l为边长的时候只需要判断这个矩阵的和是否大于等于 l ∗ l l*l ll

参考代码:

#include <bits/stdc++.h>
using namespace std;
// #define int long long
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
#define pii pair<int, int>

int n, m;
int Log2[N];
void solve() {
    cin >> n >> m;
    int a[n + 2][m + 2];
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> a[i][j];
    int ans = 0, l = 1, r = min(n, m);
    while (l <= r) {
        int mid = (l + r) >> 1;
        bool ok = 0;
        int s[n + 1][m + 1];
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                s[i][j] = (a[i][j] >= mid);
                s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + s[i][j];
            }
        }
        for (int i = 1; i + mid - 1 <= n; i++) {
            for (int j = 1; j + mid - 1 <= m; j++) {
                int x1 = i, y1 = j, x2 = i + mid - 1, y2 = j + mid - 1;
                if (s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1] >= mid * mid) {
                    ok = true;
                    break;
                }
            }
        }
        if (ok)
            l = mid + 1, ans = mid;
        else
            r = mid - 1;
    }
    cout << ans << "\n";
}
signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    for (int i = 2; i < N; i++)  // 预处理
        Log2[i] = Log2[i >> 1] + 1;
    int T = 1;
    cin >> T;
    while (T--)
        solve();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

self_disc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值