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 long using 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 ", lt); else printf("%d ", 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; }