1. 程式人生 > >Atcoder Beginner Contest 107 D Median of Medians(二分答案)(中位數)

Atcoder Beginner Contest 107 D Median of Medians(二分答案)(中位數)

題意

給出一個序列,求這個序列所有子序列中位數的中位數

思路

直接考慮非常fake

換個角度,某個數要成為中位數的中位數,那麼中位數大於等於他的序列必須佔總序列數的至少1/2,而最終答案是符合這個條件的最大的數。於是二分答案。

如何寫check函式呢?因為選出mid之後,序列中所有數的具體數值已經不重要了,所以可以把所有大於等於mid的數看作1,小於的看作-1,則某個序列的中位數如果大於等於mid,那這個區間裡1和-1的和大於等於0。這裡如果對1和-1的序列求字首和,那麼問題轉化成求d[i]<=d[j],且i < j的對數,可以用歸併或者樹狀陣列求逆序對的方法做。

要注意的是各種等於能不能取到,各種向上取整向下取整。

程式碼

//歸併寫法
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long 
using namespace std;
const ll INF = 1e18+10;
const ll N = 100010;
ll n, a[N], lim, d[N], c[N], res;

void chkMax(ll &x, ll y){if (x < y) x = y;}
void chkMin(ll &x, ll y){if (x > y) x = y;
} void Msort(ll l, ll r) { if (l == r) return; ll mid = (l+r)>>1; Msort(l, mid); Msort(mid+1, r); ll i = l, j = mid+1, k = l; while (i <= mid && j <= r){ if (d[i] <= d[j]){ c[k++] = d[i++]; res += r-j+1; } else
c[k++] = d[j++]; } while (i <= mid) c[k++] = d[i++]; while (j <= r) c[k++] = d[j++]; for (ll o = l; o <= r; o++) d[o] = c[o]; } bool Check(ll mid) { d[0] = 0; for (ll i = 1; i <= n; i++){ d[i] = d[i-1]; if (a[i] >= mid) d[i]++; else d[i]--; } res = 0; Msort(0, n); if (res >= lim) return true; return false; } int main() { cin >> n; lim = (n*(n+1)/2+1)/2; ll l = INF, r = -INF, mid, ans; for (ll i = 1; i <= n; i++){ cin >> a[i]; chkMax(r, a[i]); chkMin(l, a[i]); } while (l <= r){ mid = (l+r)>>1; if (Check(mid)) ans = mid, l = mid+1; else r = mid-1; } cout << ans; return 0; }