D. Med-imize - Codeforces Round 963 (Div. 2)

https://codeforces.com/contest/1993/problem/D

题目大意

给出一个 $n \ (le 5 \cdot 10^5)$ 长的数组 $a$ 其中 $a_i \le 10^9$。给定一个 $k \ (\le 5 \cdot 10^5)$。

不断地从数组中删去一些长为 $k$ 的子数组(删除后把数组接起来),使得最后的数组长度 $\le k$。 问此时剩余元素的中位数最大可能是多少。剩余元素个数为偶数时中位数取中间偏小的值。

简要题解

由于这个题目总是删除到最少,因此剩下的元素数量是一定的,即 $n % k$ 个,特别的当 $k | n$ 时,为 $k$ 个。 所以实际上,我们知道最终的序列中有多少个数是比中位数大的。

由此,注意到中位数的值是可以二分的。如果中位数 $\ge x$ 是有解的,那么显然取任意的 $y \le x$ 则中位数 $\ge y$ 是有解的。

如何判断能否剩下足够多个 $\ge x$ 的数呢?

首先观察到,因为我们只关注剩下来的数,因此实际上删除这个操作没必要相交。例如 $k = 3$,

$a$ 为 $1 \ 2 \ 3 \ 4 \ 5 \ 6 \ 7$

如果删掉下标 $2~4$ 变成 $1 \ 5 \ 6 \ 7$

再删掉下标 $1~3$ 变成 $7$

这与先删 $4~6$ 再删 $1~3$ 的效果是一样的。

考虑 $b_i = [a_i \ge x]$,于是问题变成了,删掉尽可能多的不相交区间保留保留尽可能多的 $1$。

当最后不是剩 $k$ 个时,考虑 $dp[i]$ 为已经考虑到 $i$ 位置删去尽可能多的区间之后剩下 $1$ 的最大数量。这样对于所有的 $k | i$

$$ \begin{equation} dp[i]=\left\{ \begin{aligned} 0, \quad & i = 0 \ or \ k | i \\ dp[i - 1] + b[i], \quad & k < i \\ max(dp[i - 1] + b[i], dp[i - k]), \quad & else \end{aligned} \right. \end{equation} $$

特别的当最后需要剩下 $k$ 个时,这种情况需要单独处理。当 $k | i$ 时,还需要让 $dp[i] = 0$,否则我们会得到选超过 $k$ 个剩下的情况。因为对应这些 $i$, $dp[i - 1]$ 的值总是正确的,因此,这时只需用 $dp[i - 1] + b[i]$ 更新一个 $ans$ 即可。

复杂度

$T$:$O(n \log max(a_i))$

$S$:$O(n)$

代码实现

#include <bits/stdc++.h>
using namespace std;

int io_=[](){ ios::sync_with_stdio(false); cin.tie(nullptr); return 0; }();

using LL = long long;
using ULL = unsigned long long;
using LD = long double;
using PII = pair<int, int>;
using VI = vector<int>;
using MII = map<int, int>;

template<typename T> void cmin(T &x,const T &y) { if(y<x) x=y; }
template<typename T> void cmax(T &x,const T &y) { if(x<y) x=y; }
template<typename T> bool ckmin(T &x,const T &y) { 
    return y<x ? (x=y, true) : false; }
template<typename T> bool ckmax(T &x,const T &y) { 
    return x<y ? (x=y, true) : false; }
template<typename T> void cmin(T &x,T &y,const T &z) {// x<=y<=z 
    if(z<x) { y=x; x=z; } else if(z<y) y=z; }
template<typename T> void cmax(T &x,T &y,const T &z) {// x>=y>=z
    if(x<z) { y=x; x=z; } else if(y<z) y=z; }

// mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
// mt19937_64 rnd_64(chrono::system_clock::now().time_since_epoch().count());

/*
---------1---------2---------3---------4---------5---------6---------7---------
1234567890123456789012345678901234567890123456789012345678901234567890123456789
*/

void solve() {
  int n, k; cin >> n >> k;
  vector<int> a(n);
  for (int& i : a) cin >> i;
  
  int left = n % k;
  int need = left ? (left / 2) + 1 : (k / 2) + 1;

  // cerr << "need " << need << endl;
  
  vector<int> dp(n + 1);
  auto check = [&](int mid) -> bool {
    fill(dp.begin(), dp.end(), 0);

    int mx = 0;
    
    for (int i = 1; i <= n; i++) {
      if (i % k == 0) {
        cmax(mx, dp[i - 1] + (a[i - 1] >= mid));
        dp[i] = 0;
        continue;
      }

      dp[i] = dp[i - 1] + (a[i - 1] >= mid);

      if (i >= k) {
        cmax(dp[i], dp[i - k]);
      }
    }

    if (left) return dp[n] >= need;
    else return mx >= need;
  };

  int l = 1, r = *max_element(a.begin(), a.end());
  int mid, ans = l;

  while (l <= r) {
    mid = (l + r) >> 1;
    if (check(mid)) {
      ans = mid;
      l = mid + 1;
    } else {
      r = mid - 1;
    }
  }
  cout << ans << '\n';
}

int main() {
  int t = 1; 
  cin >> t;
  while (t--) {
    solve();
  }
  return 0;
}
Prev: E. Level Up - Educational Codeforces Round 168
Next: C. Light Switches - Codeforces Round 963 (Div. 2)