P5781 [IOI 2019] 矩形区域

· · 题解

大牛逼题,但是刚开始读错题了,模拟赛题面是“矩形内的位置的愉悦值严格小于其边界上的愉悦值”,你能不读错???以为是矩形 \max 小于边界 \min,比较困难。

思路:

暴力无法通过,考虑找一些性质。

这个矩形太大了,不会找性质,考虑缩放到每一行每一列,只考虑这一行的性质,容易发现:

那么来考虑这个大矩形,每行每列都满足这样的性质就是判定的一个充要条件;于是我们可以先找到所有的行 (i, l, r) 合法,显然不会超过 2nm 个,我们可以令 S_{l, r} 表示所有的 i 使得 (i, l, r) 合法的集合。

先考虑枚举这个大矩形的左右边界 [l, r],那么满足行性质的上下边界必然是 S_{l, r} 中所有连续段的子区间,但是这可能很多,无法快速进行判断。

于是考虑再次基础上加上列的性质,当我们枚举 r 时,先算出列 r 的所有合法 [r, x, y],然后去更新 f_{x, y} 表示矩形的上下边界为 [x, y],右边界为 r 时只满足列的性质时左端点最远是多少:

那么对于所有 S_{l, r} 的连续段 [L, R] 计算 [L, R] 里面符合列性质的区间 [l', r'] 判断 f_{l', r'} \le l 是否可以贡献即可。

找合法段跑单调栈即可。

每个 S_{l, r} 的连续段只会跑一次,于是时间复杂度为 O(nm)

完整代码:

 #include<bits/stdc++.h>
#define ls(k) k << 1
#define rs(k) k << 1 | 1
#define fi first
#define se second
#define add(x, y) ((x + y >= mod) ? (x + y - mod) : (x + y))
#define dec(x, y) ((x - y < 0) ? (x - y + mod) : (x - y))
#define popcnt(x) __builtin_popcount(x)
#define open(s1, s2) freopen(s1, "r", stdin), freopen(s2, "w", stdout);
using namespace std;
typedef __int128 __;
typedef long double lb;
typedef double db;
typedef unsigned long long ull;
typedef long long ll;
bool Begin;
const int N = 2520;
inline ll read(){
    ll x = 0, f = 1;
    char c = getchar();
    while(c < '0' || c > '9'){
        if(c == '-')
          f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }
    return x * f;
}
inline void write(ll x){
    if(x < 0){
        putchar('-');
        x = -x;
    }
    if(x > 9)
      write(x / 10);
    putchar(x % 10 + '0');
}
ll ans;
int n, m, cnt, top;
int stk[N];
int a[N][N], f[N][N], tim[N][N];
pair<int, int> ac[N];
vector<int> S[N][N];
inline void solve(int *h, int n){
    cnt = top = 0;
    for(int i = 1; i <= n; ++i){
        while(top && h[i] > h[stk[top]]){
            if(i > stk[top] + 1)
              ac[++cnt] = {stk[top] + 1, i - 1}; // l < r
            --top;
        }
        if(top){
            if(i > stk[top] + 1)
              ac[++cnt] = {stk[top] + 1, i - 1}; // l > r
            if(h[i] == h[stk[top]])
              --top;
        }
        stk[++top] = i;
    }
}
inline void solve(int l, int r, int x, int y){
    int siz = 0;
    for(int i = x - 1; i <= y + 1; ++i)
      a[0][++siz] = a[i][r];
    solve(a[0], siz);
    for(int i = 1; i <= cnt; ++i){
        int L = ac[i].fi + x - 2, R = ac[i].se + x - 2;
        if(f[L][R] <= l)
          ++ans;
    }
}
bool End;
int main(){
    freopen("field.in", "r", stdin);
    freopen("field.out", "w", stdout);
    n = read(), m = read();
    for(int i = 1; i <= n; ++i)
      for(int j = 1; j <= m; ++j)
        a[i][j] = read();
    for(int i = 2; i < n; ++i){
        solve(a[i], m);
        for(int j = 1; j <= cnt; ++j)
          S[ac[j].fi][ac[j].se].push_back(i); 
    }
    for(int r = 2; r < m; ++r){
        for(int i = 1; i <= n; ++i)
          a[0][i] = a[i][r];
        solve(a[0], n);
        for(int i = 1; i <= cnt; ++i){
            if(tim[ac[i].fi][ac[i].se] + 1 < r)
              f[ac[i].fi][ac[i].se] = r;
            tim[ac[i].fi][ac[i].se] = r;
        }
        for(int l = 2; l <= r; ++l){
            if(S[l][r].empty())
              continue;
            int lst = S[l][r][0], siz = S[l][r].size();
            for(int i = 1; i < siz; ++i){
                if(S[l][r][i] > S[l][r][i - 1] + 1){
                    solve(l, r, lst, S[l][r][i - 1]);
                    lst = S[l][r][i];
                }
            }
            solve(l, r, lst, S[l][r][siz - 1]);
        }
    }
    write(ans);
    //cerr << '\n' << abs(&Begin - &End) / 1048576 << "MB";
    return 0;
}