http://www.51nod.com/contest/problem.html#!problemId=1685
这是这次BSG白山极客挑战赛的E题。
这题可以二分答案t。
关键在于,对于一个t,如何判断它是否能成为第k大。
将序列中大于t的置为1,小于t的置为-1,等于t的置为0。那么区间中位数大于t的和就大于0,小于t的就小于0。于是就是判断区间和大于0的个数是否小于等于k。
维护前缀和sum(i),然后统计之前sum(j)小于sum(i)的有多少个,就是以i为右值的区间和大于0的个数。于是就可以用树状数组维护了。
由于是奇数长度区间,所以树状数组需要维护奇偶长度的前缀和个数。需要特判sum(i) > 0的情况。
代码:
#include <iostream> #include <cstdio> #include <cstdlib> #include <cmath> #include <cstring> #include <algorithm> #include <set> #include <map> #include <queue> #include <vector> #include <string> #define LL long longusing namespace std;//线段树 //区间每点增值,求区间和 const int maxN = 100010;LL d[2][maxN*2];int lowbit(int x) {return x&(-x); }void add(int t, int id,int pls) {while(id <= maxN<<1)//id最大是maxN {d[t][id] += pls;id += lowbit(id);} }LL sum(int t, int to) {LL s = 0;while(to > 0){s = s + d[t][to];to -= lowbit(to);}return s; }LL query(int t, int from, int to) {return sum(t, to) - sum(t, from-1); }int n, a[maxN], b[maxN]; LL k;LL judge(int t) {for (int i = 0; i < n; ++i){if (a[i] > t) b[i] = 1;else if (a[i] == t) b[i] = 0;else b[i] = -1;}memset(d, 0, sizeof(d));int sum = 0;LL ans = 0;for (int i = 0; i < n; ++i){sum += b[i];ans += query(!(i%2), 100005-n, 100005+sum-1);if (i%2 == 0 && sum > 0) ans++;add(i%2, 100005+sum, 1);}return ans; }void work() {int lt, rt, mid;lt = rt = a[0];for (int i = 1; i < n; ++i){lt = min(lt, a[i]);rt = max(rt, a[i]);}while ((LL)lt+1 < rt){mid = ((LL)lt+rt)>>1;if (judge(mid) > k-1) lt = mid;else rt = mid;}if (judge(lt) <= k-1) printf("%d\n", lt);else printf("%d\n", rt); }int main() {//freopen("test.in", "r", stdin);while (scanf("%d", &n) != EOF){cin >> k;for (int i = 0; i < n; ++i) scanf("%d", &a[i]);work();}return 0; }