LuoguP7715 「EZEC-10」Shape 题解

Content

有一个 \(n\times m\) 的网格,网格上的格子被涂成了白色或者黑色。

设两个点 \((x_1,y_1)\)\((x_2,y_2)\),如果以下三个条件均满足:

  • \(1\leqslant x_1<x_2\leqslant n\)\(1\leqslant y_1<y_2\leqslant m\)
  • \(2\mid (x_1+x_2)\)
  • \((x_1,y_1)\rightarrow(x_2,y_1)\)\((x_1,y_2)\rightarrow(x_2,y_2)\)\((\dfrac{x_1+x_2}2,y_1)\rightarrow(\dfrac{x_1+x_2}2,y_2)\) 这三段中的格子均为白色。

那么我们称第三个条件中的三段构成的图形为 H 形。求 \(n\times m\) 的网格里面有多少个不同的 H 形。

数据范围:\(2\leqslant n,m\leqslant 2\times 10^3\)

Solution

本题解分 Subtask 讲解。

Subtask 1 (1 pt):\(n=2\)

在这个 Subtask 中,我们无论如何选择两个点都不能同时满足前两个条件。因此答案是 \(0\)

Subtask 2 (9 pts):\(n,m\leqslant 50\)

我们可以用 \(\mathcal O(n^2m^2)\) 枚举出两个点 \((x_1,y_1)\)\((x_2,y_2)\),然后我们直接枚举三段并判断是否三段都是白色格子,最后如果三个条件都满足的话,将其统计入答案即可。

Subtask 3&4 (40 pts):\(n,m\leqslant 500\)

我们发现我们直接枚举两个点的话太耗费时间了,能否有更快的方法?

我们为什么不枚举两个中间点呢?因为这两个点都在同一行,枚举的复杂度可以降至 \(\mathcal O(nm^2)\)

但是如果再去枚举三段的话又太浪费时间了,能否有更快的方法?

这时候预处理就派上用场了。我们可以先预处理出点 \((i,j)\) 向上最多能够到达的连续 \(0\) 的个数 \(s_{i,j}\) 和向下最多能够到达的连续 \(0\) 的个数 \(d_{i,j}\)。可以通过枚举坐标再去向上向下直接暴力推,复杂度是 \(\mathcal O(n^2m)\) 的。

然后枚举中间点的时候就可以直接求出两个中间点 \((a,b)\)\((c,d)\) 能够得到的 H 形的个数为 \(\min\{s_{a,b},d_{a,b},s_{c,d},d_{c,d}\}\)。直接统计入答案即可。

Subtask 5 (50 pts):正解

我们发现 \(n,m\leqslant 2\times 10^3\) 的时候,上面的算法的复杂度还是太大了。能否有更快的方法?

一开始想能否用线段树/树状数组处理,然而还是我太 naive 了没有想出来。后来想到了一个可以用栈来处理的方法。

我们直接扫一遍这个网格,每逢被涂成黑色的格子或者到了一行的尽头,就把这个栈里头的所有元素拿出来,按照从小到大的顺序排个序,然后每一个在栈里头的元素 \(st_k\),其对答案的贡献是 \(st_k\times (top-k)\)(其中 \(top\) 指原来栈里面的元素个数),把这个贡献加入里面去并清空整个栈。否则将 \(f_{i,j}=min\{s_{i,j},d_{i,j}\}\) 放入栈里头,复杂度大概是 \(\mathcal O(nm\log m)\) 的?

预处理也可以变成 \(\mathcal O(nm)\) 的,具体的就不多做赘述了,看代码吧。

Code

const int N = 2007;
int n, m, a[N][N], s[N][N], d[N][N], f[N][N];

namespace sub1 {
    iv work() {return printf("0"), void();}
}
namespace sub2 {
    iv work() {
        int ans = 0;
        F(int, x, 1, n) F(int, y, 1, m) Fo(int, x_, x + 2, n, 2) F(int, y_, y + 1, m) {
            int fl = 1;
            F(int, i, x, x_) if(a[i][y]) {fl = 0; break;}
            F(int, i, x, x_) if(a[i][y_]) {fl = 0; break;}
            F(int, j, y, y_) if(a[(x + x_) / 2][j]) {fl = 0; break;}
            ans += fl;
        }
        return write(ans), void();
    }
}
namespace sub3 {
    iv work() {
        int ans = 0;
        F(int, i, 1, n) F(int, j, 1, m) if(!a[i][j]) F(int, k, j + 1, m) if(!a[i][k]) {
            ans += min(min(min(s[i][j], d[i][j]), s[i][k]), d[i][k]);
        } else break;
        return write(ans), void();
    }
}
namespace sub4 {
    int st[N], top;
    ll ans = 0;
    iv work() {
        F(int, i, 1, n) {
            top = 0;
            F(int, j, 1, m + 1) if(a[i][j] == 1 || j == m + 1) {
                if(top) {
                    sort(st + 1, st + top + 1)/*, print_array1(st, top), puts("-------");*/;
                    F(int, k, 1, top - 1) ans += 1ll * st[k] * (top - k);
                    top = 0; 
                }
            } else st[++top] = f[i][j];
        }
        return write(ans), void();
    }
}

int main() {
    n = Rint, m = Rint;
    F(int, i, 1, n) F(int, j, 1, m) a[i][j] = Rint;
    // F(int, i, 1, n) F(int, j, 1, m) if(!a[i][j]) {
    //     F(int, k, i + 1, n) if(a[k][j] == 0) s[i][j]++; else break;
    //     R(int, k, i - 1, 1) if(a[k][j] == 0) d[i][j]++; else break;
    //     f[i][j] = min(s[i][j], d[i][j]);
    // } //这是 O(n^2m) 的预处理
    F(int, j, 1, m ) R(int, i, n, 1) s[i][j] = (a[i][j] == 0 ? ((a[i + 1][j] == 1 || i == n) ? 0 : s[i + 1][j] + 1) : 0);
    F(int, j, 1, m) F(int, i, 1, n) d[i][j] = (a[i][j] == 0 ? ((a[i - 1][j] == 1 || i == 1) ? 0 : d[i - 1][j] + 1) : 0);
    F(int, i, 1, n) F(int, j, 1, m) f[i][j] = min(s[i][j], d[i][j]); //这是 O(nm) 的预处理
    if(n == 2) sub1 :: work();
    else if(n <= 100 && m <= 100) sub2 :: work();
    else if(n <= 500 && m <= 500) sub3 :: work();
    else sub4 :: work();
    return 0;
}
posted @ 2021-12-15 21:39  Eason_AC  阅读(49)  评论(0)    收藏  举报