競技プログラミング日記

主に AtCoder の記事です

AtCoder Beginner Contest 203D問題

ABC203D

解法

答えの候補を二分探索する. 集合 \(A\) に対して, \(min(A) \leq x\) である事は, \(A\)のある元で \(x\) 以下となるものが存在することと同地. また, \(min(A) \geq x\) である事は, \(A\)の任意の元が \(x\) 以上であることと同値. いずれを使ってもよい.
答えの候補\(x\)を固定すると,指定された範囲の median を調べるためには, 値が \(x\) 以下の情報しか関係ないことになる. \(x\) 以下の個数を数えて,半分より多いかどうかを調べれば, median が \(x\) 以下となる事が判定出来る.
\(x\)以下の元の個数を何度も数える事になるので, 累積和を使う.\(x\)以下ならば 1, そうでなければ 0としておくと, \(x\)以下の元の個数は累積和と一致する.

注意

Median の定義が,問題文で与えられているものがどうなっているか注意. 大きい方から数えるのか,小さい方から数えるのか.

使っている記号,マクロ等 "https://ecsmtlir.hatenablog.com/entry/2022/12/23/131925"

int main() {
  ll n, k;
  cin >> n >> k;
  vvll a(n, vll(n)); cinvv(a);

  // x [以下]
  auto cum = [&](ll x) -> vvll {
    vvll s(n+1, vll(n+1));

    rep(i,n) rep(j,n){
      s[i+1][j+1] = (a[i][j] <= x)
                    + s[i][j+1] + s[i+1][j]
                    - s[i][j];
    }

    return s;
  };

  // x [以上]
  auto cum2 = [&](ll x) -> vvll {
    vvll s(n+1, vll(n+1));

    rep(i,n) rep(j,n){
      s[i+1][j+1] = (a[i][j] >= x)
                    + s[i][j+1] + s[i+1][j]
                    - s[i][j];
    }

    return s;
  };
 
  // s: x 以下を 1にしたときの累積和
  // judge: median の min が x [以下] に出来るか.
  //        i.e. あるkxk の領域が存在して,median が x [以下] にできる
  auto judge = [&](const vvll &s) -> bool {

    rep(i,n-k+1) rep(j,n-k+1){
      ll cnt = 0;
      cnt += s[i+k][j+k];
      cnt -= s[i+k][j];
      cnt -= s[i][j+k];
      cnt += s[i][j];
      assert(in(cnt, 0LL, k*k+1));

      // Rem: median の定義は,
      //       大きいほうから floor(k*k/2) + 1 番目
      //     = 小さいほうから floor(k*k/2) + (k%2) 番目.
      if(cnt >= k*k/2 + (k%2)) return true;
    }
    return false;
  };

  // s: x 以下を 1にしたときの累積和
  // judge: median の min が x [以上] にできるか.
  //        i.e.すべての kxk の領域に対して,median が x [以上] である.
  auto judge2 = [&](const vvll &s) -> bool {

    rep(i,n-k+1) rep(j,n-k+1){
      ll cnt = 0;
      cnt += s[i+k][j+k];
      cnt -= s[i+k][j];
      cnt -= s[i][j+k];
      cnt += s[i][j];
      assert(in(cnt, 0LL, k*k+1));

      // Rem: 大きいほうから floor(k*k/2) + 1 番目
      if(cnt < k*k/2 + 1) return false;
    }
    return true;
  };

 

  // ll ok = 1e9, ng = -1;
  ll ok = 0, ng = (ll)1e9 + 1;
  while(abs(ok - ng) > 1){
    ll mid = (ok + ng) / 2;
    // vvll s = cum(mid);
    vvll s = cum2(mid);

    // if(judge(s)) ok = mid;
    if(judge2(s)) ok = mid;
    else ng = mid;
  }
  cout << ok << endl;

  return 0;
}