1 条题解

  • 0
    @ 2026-5-16 15:11:21

    题目解析

    给定数组 aa(元素在 11nn 之间)和整数 kk,定义 vv子中位数当且仅当存在长度至少为 kk 的子数组,使得 vv 是该子数组的一个中位数(中位数定义见题目)。需要找出所有子中位数,并为每个子中位数输出任意一个对应的子数组。

    关键性质

    1. 单调性:如果 xxyy 都是子中位数且 xyx \le y,那么区间 [x,y][x, y] 内的所有整数都是子中位数。因此,子中位数构成一个连续整数区间 [L,R][L, R],其中 LL 是最小子中位数,RR 是最大子中位数。

    2. 最大子中位数:可以用二分法求出。对于固定 vv,定义 bi=1b_i = 1aiva_i \ge v,否则 bi=1b_i = -1。则存在长度 k\ge k 的子数组使 vv 成为中位数的必要条件是该子数组的 bb 之和 0\ge 0(因为 v\ge v 的元素不少于一半)。实际上,对于最大子中位数,这个条件也是充分的(证明见 E1 题解)。因此我们可以二分 vv 并检查是否存在长度 k\ge k 的子数组满足 bi0\sum b_i \ge 0,同时记录找到的区间。

    3. 最小子中位数:对称地,定义 ci=1c_i = 1aiva_i \le v,否则 ci=1c_i = -1,则存在子数组使 vv 成为中位数等价于 ci0\sum c_i \ge 0。同样可以二分求出最小子中位数 LL

    4. 为每个中间值构造区间:从 LL 对应的区间 [lL,rL][l_L, r_L] 出发,通过逐步向左、向右扩展窗口(每次增加一个端点),窗口长度单调增加,中位数区间也会连续变化,从而覆盖 [L,R][L, R] 中的所有整数。在扩展过程中,记录每个新出现的中位数对应的当前窗口区间即可。

    算法步骤

    对于每个测试用例:

    1. 使用二分法找到最大子中位数 RR 和对应区间 [lR,rR][l_R, r_R]
    2. 使用二分法找到最小子中位数 LL 和对应区间 [lL,rL][l_L, r_L]
    3. 初始化窗口为 [lL,rL][l_L, r_L],用两个 multiset(或堆)维护当前窗口的较小一半和较大一半,以便快速得到当前窗口的中位数区间。
    4. 记录当前中位数区间内的所有尚未记录的值,并保存当前窗口作为它们的答案。
    5. 不断扩展窗口:先向左扩展(ll1l \leftarrow l-1)直到 l=1l=1,再向右扩展(rr+1r \leftarrow r+1)直到 r=nr=n。每次扩展后重新计算中位数区间,记录新出现的值。
    6. 输出所有子中位数 v[L,R]v \in [L, R] 及其对应的区间。

    复杂度分析

    • 二分查找 O(logn)O(\log n) 次,每次检查 O(n)O(n),总 O(nlogn)O(n \log n)
    • 扩展窗口时,端点移动总次数 O(n)O(n),每次插入/删除 O(logn)O(\log n),总 O(nlogn)O(n \log n)
    • 所有测试用例的 nn 之和不超过 3×1053\times 10^5,因此总复杂度可接受。

    代码实现

    #include <bits/stdc++.h>
    using namespace std;
    using ll = long long;
    
    // 检查是否存在长度≥k的子数组使得≥v的元素不少于一半,并返回一个可行区间(如果存在)
    bool check_max(int v, int n, int k, const vector<int>& a, int& out_l, int& out_r) {
        vector<int> b(n);
        for (int i = 0; i < n; ++i) b[i] = (a[i] >= v) ? 1 : -1;
        vector<int> pref(n+1, 0);
        for (int i = 0; i < n; ++i) pref[i+1] = pref[i] + b[i];
        int min_pref = pref[0];
        int min_pos = 0;
        for (int r = k; r <= n; ++r) {
            int idx = r - k;
            if (pref[idx] < min_pref) {
                min_pref = pref[idx];
                min_pos = idx;
            }
            if (pref[r] >= min_pref) {
                out_l = min_pos + 1; // 转为1-index
                out_r = r;
                return true;
            }
        }
        return false;
    }
    
    // 检查是否存在长度≥k的子数组使得≤v的元素不少于一半
    bool check_min(int v, int n, int k, const vector<int>& a, int& out_l, int& out_r) {
        vector<int> b(n);
        for (int i = 0; i < n; ++i) b[i] = (a[i] <= v) ? 1 : -1;
        vector<int> pref(n+1, 0);
        for (int i = 0; i < n; ++i) pref[i+1] = pref[i] + b[i];
        int min_pref = pref[0];
        int min_pos = 0;
        for (int r = k; r <= n; ++r) {
            int idx = r - k;
            if (pref[idx] < min_pref) {
                min_pref = pref[idx];
                min_pos = idx;
            }
            if (pref[r] >= min_pref) {
                out_l = min_pos + 1;
                out_r = r;
                return true;
            }
        }
        return false;
    }
    
    // 维护两个有序集合,模拟当前窗口的较小一半和较大一半
    struct MedianTracker {
        multiset<int> small, large;
        int len = 0;
        int target() const { return (len + 1) / 2; } // ceil(len/2)
        void balance() {
            while (small.size() > target()) {
                auto it = prev(small.end());
                large.insert(*it);
                small.erase(it);
            }
            while (small.size() < target() && !large.empty()) {
                auto it = large.begin();
                small.insert(*it);
                large.erase(it);
            }
            // 保证 small 中的所有元素 <= large 中的所有元素
            if (!small.empty() && !large.empty() && *small.rbegin() > *large.begin()) {
                int x = *small.rbegin(), y = *large.begin();
                small.erase(prev(small.end()));
                large.erase(large.begin());
                small.insert(y);
                large.insert(x);
            }
        }
        void insert(int val) {
            if (small.empty() || val <= *small.rbegin()) small.insert(val);
            else large.insert(val);
            ++len;
            balance();
        }
        void erase(int val) {
            auto it = small.find(val);
            if (it != small.end()) small.erase(it);
            else {
                it = large.find(val);
                if (it != large.end()) large.erase(it);
            }
            --len;
            balance();
        }
        // 返回当前窗口的中位数区间 [low, high]
        pair<int,int> median_range() {
            if (len == 0) return {0,0};
            int low = *small.rbegin();
            int high = (len % 2 == 0 && !large.empty()) ? *large.begin() : low;
            return {low, high};
        }
    };
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(0);
        int t;
        cin >> t;
        while (t--) {
            int n, k;
            cin >> n >> k;
            vector<int> a(n);
            for (int i = 0; i < n; ++i) cin >> a[i];
            // 1. 找最大子中位数 R
            int lo = 1, hi = n, R = 1, lR = 0, rR = 0;
            while (lo <= hi) {
                int mid = (lo + hi) / 2;
                int ltmp, rtmp;
                if (check_max(mid, n, k, a, ltmp, rtmp)) {
                    R = mid;
                    lR = ltmp; rR = rtmp;
                    lo = mid + 1;
                } else {
                    hi = mid - 1;
                }
            }
            // 2. 找最小子中位数 L
            lo = 1, hi = n;
            int L = 1, lL = 0, rL = 0;
            while (lo <= hi) {
                int mid = (lo + hi) / 2;
                int ltmp, rtmp;
                if (check_min(mid, n, k, a, ltmp, rtmp)) {
                    L = mid;
                    lL = ltmp; rL = rtmp;
                    lo = mid + 1;
                } else {
                    hi = mid - 1;
                }
            }
            // 3. 滑动窗口,为 [L, R] 中每个值记录区间
            vector<int> ans_l(n+1, -1), ans_r(n+1, -1);
            MedianTracker tracker;
            // 初始化窗口为 [lL, rL](1-indexed)
            int l = lL, r = rL;
            for (int i = l; i <= r; ++i) tracker.insert(a[i-1]);
            auto [low, high] = tracker.median_range();
            for (int v = low; v <= high; ++v) {
                if (ans_l[v] == -1) {
                    ans_l[v] = l;
                    ans_r[v] = r;
                }
            }
            // 向左扩展
            while (l > 1) {
                --l;
                tracker.insert(a[l-1]);
                auto [nl, nh] = tracker.median_range();
                for (int v = nl; v <= nh; ++v) {
                    if (ans_l[v] == -1) {
                        ans_l[v] = l;
                        ans_r[v] = r;
                    }
                }
            }
            // 向右扩展(此时 l 已到 1)
            while (r < n) {
                ++r;
                tracker.insert(a[r-1]);
                auto [nl, nh] = tracker.median_range();
                for (int v = nl; v <= nh; ++v) {
                    if (ans_l[v] == -1) {
                        ans_l[v] = l;
                        ans_r[v] = r;
                    }
                }
            }
            // 输出答案
            int cnt = R - L + 1;
            cout << cnt << "\n";
            for (int v = L; v <= R; ++v) {
                cout << v << " " << ans_l[v] << " " << ans_r[v] << "\n";
            }
        }
        return 0;
    }
    
    • 1

    信息

    ID
    7108
    时间
    1000ms
    内存
    256MiB
    难度
    6
    标签
    递交数
    1
    已通过
    1
    上传者