COCI 2025/2026 做题记录

COCI 2025/2026 做题记录

#6 教室 / Učionica

复健第一题。写了个线段树调半天才过,然后看题解才发现有个非常好写的二分做法,呃呃。

首先对 \(a_{i, j}\) 的每一列取前缀 \(\max\):令

\[b_{i, j} = \max_{1 \le l \le i} \{a_{l, j}\} \]

则第 \(l\) 个朋友可以坐在 \((i, j)\) 位置,当且仅当 \(a_{i, j} = 0\) 且 \(h_l > b_{i, j}\)。

考虑一段长度为 \(k\) 的连续空座位区间,其 \(b\) 值对应为 \((c_1, c_2, \cdots, c_k)\)。如何判定 \(k\) 个朋友能否坐在这段座位上,使得每人都有清晰的视线?

这个问题等价于,是否存在 \(h\) 的一种排列方式 \(h'\),使得 \(\forall 1 \le i \le k\),\(h'_i > c_i\)。

定义:给定两个长度均为 \(n\) 的数列 \(a\) 和 \(b\),记 \(a \prec b\) 表示 \(\forall 1 \le i \le n\),\(a_{i} < b_{i}\)。

则上面的问题可以记为:是否存在 \(h\) 的一种排列方式 \(h'\),使得 \(c \prec h'\)。

引理 1:给定两个长度均为 \(n\) 的数列 \(a\) 和 \(b\),以下两个条件等价:

  1. 存在 \(b\) 的重排方式 \(b'\),使得 \(a \prec b'\);
  2. 设 \(a'\) 和 \(b'\) 分别为 \(a\) 和 \(b\) 从小到大排列后的结果,\(a' \prec b'\)。

回到原问题的情景,这个引理就是说,让最矮的朋友去坐 \(b\) 值最小的座位,次矮的朋友去坐 \(b\) 值次小的座位……如果这种方案可以让每个朋友都有清晰的视线,那么这段空座位区间就满足条件,否则不满足。

可以用调整法简单证明:设\(a \prec b\),\(a\) 数列已经从小到大排序,\(b\) 数列存在逆序对 \((i, j)\)(即 \(i < j\) 且 \(b_i > b_j\))。由 \(a \prec b\) 得 \(b_i > a_i\) 且 \(b_j > a_j\)。那么

\[b_j > a_j > a_i \]

且

\[b_i > b_j > a_j \]

所以交换 \(b_i\) 和 \(b_j\) 后,\(a \prec b\) 仍然成立。重复这个步骤直到 \(b\) 不存在逆序对,即 \(b\) 也是有序的。证毕。

回到题目。对于每一行,用一个长度为 \(k\) 的“窗口”去框住一段座位。如果这一段座位有人坐,则不满足条件。否则设框到的座位 \(b\) 值为 \(c = (c_1, c_2, \cdots, c_k)\)。窗口从最左端向右移动,每移动一格,就从 \(c\) 中删去一个数,再增加一个数。现在我们的任务是,在有修改的条件下,维护 \(c \prec h\) 是否成立。

自然的想法是维护排序后的 \(c\) 数列,逐个判断 \(c_i < h_i\) 是否成立,但这个过程不好优化。这里要做一些转化。

引理 2:给定两个长度均为 \(n\) 的有序数列 \(a\) 和 \(b\),设 \(s_i\) 表示 \(a\) 中有多少数比 \(b_i\) 小,则

\[a \prec b \iff \forall 1 \le i \le n, s_i \ge i \]

该引理显然成立。

这个转化有什么用呢?我们把 \(b\) 矩阵中的数按以下离散化:

如果 \(h_i \le x < h_{i + 1}\),则 \(x \gets i\)。(边界条件:令 \(h_0 = 0\),\(h_{k + 1} = +\infty\)。)

对于离散化后的一段连续座位,记 \(\operatorname{cnt}_{i}\) 表示 \(i\) 的数量,\(s\) 表示 \(\operatorname{cnt}\) 的前缀和,则 \(s_{i - 1}\) 就表示有多少个数小于 \(h_{i}\)。 那么这段座位满足条件,等价于 \(\forall 1 \le i \le k\),\(s_{i} \ge i + 1\)。这里我们就看到这样转换的好处了:删除或增添座位只修改 \(\operatorname{cnt}\) 的 \(1\) 个位置,对应 \(s\) 上的一段区间修改。于是我们只需判定修改后 \(s_{i} - i - 1\) 是否总不小于 \(0\)。这容易用一个支持区间加减和全局最值的数据结构(如线段树)维护。

易知时间复杂度为 \(O(nm \log k)\)。

参考代码
#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

struct SGT {
    #define lson (id << 1)
    #define rson (id << 1 | 1)

    const int n;
    vector<int> info, tag;

    void update(int id) {
        info[id] = min(info[lson], info[rson]);
    }

    void set(int id, int x) {
        info[id] += x;
        tag[id] += x;
    }

    void pushdown(int id) {
        set(lson, tag[id]), set(rson, tag[id]);
        tag[id] = 0;
    }

    SGT(const vector<int> &init): n((int)init.size()), info(n << 2), tag(n << 2) {
        function<void(int, int, int)> build = [&](int id, int l, int r) {
            if(l + 1 == r) {
                info[id] = init[l];
                return;
            }
            int mid = (l + r) >> 1;
            build(lson, l, mid), build(rson, mid, r);
            update(id);
        };
        build(1, 0, n);
    }

    void add(int id, int l, int r, int L, int R, int x) {
        if(l == L && r == R) {
            set(id, x);
            return;
        }
        int mid = (l + r) >> 1;
        pushdown(id);
        if(R <= mid) {
            add(lson, l, mid, L, R, x);
        } else if(L >= mid) {
            add(rson, mid, r, L, R, x);
        } else {
            add(lson, l, mid, L, mid, x);
            add(rson, mid, r, mid, R, x);
        }
        update(id);
    }

    void add(int L, int R, int x) {
        add(1, 0, n, L, R, x);
    }

    int query() {
        return info[1];
    }

    #undef lson
    #undef rson
};

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);

    int n, m, k;
    cin >> n >> m >> k;
    vector<int> h(k + 1);
    for(int i = 1; i <= k; i++) {
        cin >> h[i];
    }
    vector<vector<int>> a(n, vector<int>(m));
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < m; j++) {
            cin >> a[i][j];
        }
    }

    sort(h.begin(), h.end());
    vector<vector<int>> b(a);
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < m; j++) {
            auto it = upper_bound(h.begin(), h.end(), b[i][j]);
            int p = (int)distance(h.begin(), it);
            b[i][j] = p - 1;
        }
    }

    for(int j = 0; j < m; j++) {
        for(int i = 1; i < n; i++) {
            b[i][j] = max(b[i][j], b[i - 1][j]);
        }
    }

    int ans = 0;
    for(int i = 0; i < n; i++) {
        vector<int> cnt(k + 1, -1);
        cnt[k] = 0;
        int num = 0;
        for(int j = 0; j < k - 1; j++) {
            cnt[b[i][j]]++;
            num += a[i][j] > 0;
        }
        for(int j = 1; j <= k; j++) {
            cnt[j] += cnt[j - 1];
        }
        SGT tr(cnt);
        for(int j = k - 1; j < m; j++) {

            if(j >= k) {
                tr.add(b[i][j - k], k + 1, -1);
                num -= (a[i][j - k] > 0);
            }
            tr.add(b[i][j], k + 1, 1);
            num += (a[i][j] > 0);
            if(tr.query() >= 0 && !num) {
                ans++;
            }
        }
    }
    cout << ans << '\n';

    return 0;
}

提交记录

posted @ 2026-09-22 16:50  DengStar  阅读(15)  评论(0)    收藏  举报