ARC101 D Median of Medians
問題
https://atcoder.jp/contests/arc101/tasks/arc101_b
定義
を昇順にソートして得られる数列を とする。 このとき、 の 番目の要素の値を、b の中央値とする。 ここで、/ は小数点以下を切り捨てる除算である。
解法
- 求める中央値が以下となるかどうか
という判定条件を考える。
この判定条件が初めてtrueになる境界を二分探索で求めていく。
与えられる数列 の区間 は、 の長さを とおくと、 個ある。
例) の区間は、 の = 6通り
「考えられうる区間の半数」以上、すなわち 「 個以上の区間」の中央値が 以下であれば、 となる。
「区間の中央値が 以下がどうか」は、特に区間をsortせずとも、
- 以上の要素を1, 未満の要素を-1に変換
- 区間の総和が0未満となるかどうか
で判定できる。
区間の総和は、累積和を使うと処理しやすい。
- = 0
- =
- =
- =
とおくと、 区間 の総和は と求めることができる。
よって、問題は次のように言い換えることができる。
- 数列 の要素のうち、 以上の要素を 、 未満の要素を へと変換した数列 を とおく。
- の累積和数列 において、 となる区間 の数が 以上であるような、最小の を求めよ。
ここで、
において、 となる区間 の数
は、 の転倒数を求めていることとほぼ同義である。
ここの転倒数を求める処理で、愚直に
for l in 0..n as usize { for r in l..n as usize { if S[r] < S[l] { cnt += 1 } } }
とすると、 となり、実行時間切れとなる。 Fenwick Tree等を用いて、 あたりまで計算量を下げることが求められる。
Fenwick Treeを用いた転倒数算出については別エントリに記載した。
// https://atcoder.jp/contests/arc101/tasks/arc101_b use proconio::input; // nearly exceeds 10^9 const A_MAX: i64 = 1<<30; struct Fenwick { tree: Vec<i64>, } impl Fenwick { /// create Fenwick Tree by given length pub fn new(len: usize) -> Self { Self { tree: vec![0; len] } } /// add `w` to index `i` /// `i` is 1-indexed pub fn add(&mut self, mut i: usize, w: i64) { while i < self.tree.len() { self.tree[i] += w; i += lsb(i); } } /// sum `[1, a]` /// `a` is 1-indexed pub fn sum(&self, mut i: usize) -> i64 { let mut res = 0; while i > 0 { res += self.tree[i]; i -= lsb(i); } res } /// sum `[a, b)` /// a and b are 1-indexed pub fn partial_sum(&self, a: usize, b: usize) -> i64 { self.sum(b-1) - self.sum(a-1) } } /// return least significant bit of i fn lsb(i: usize) -> usize { if i == 0 { 0 } else { i & !(i - 1) } } fn main() { input! { n: i64, mut a: [i64; n] } if n == 1 { println!("{}", a[0]); return } let mut left = 0; let mut right = A_MAX; let geta = n + 1; while right - left > 1 { let mid = (right + left) / 2; let mut inversion_number = 0; let mut ft = Fenwick::new(n as usize * 2 + 10); let mut sum = 0; ft.add((sum + geta) as usize, 1); for i in 0..n as usize { let val = if a[i] <= mid { 1 } else { -1 }; sum += val; inversion_number += ft.partial_sum(1, (sum+geta) as usize); ft.add((sum+geta) as usize, 1); } if inversion_number as i64 > (n+1)*n/2/2 { right = mid; } else { left = mid; } } println!("{}", right); }