洛谷 P1168 中位数
题目描述
给出一个长度为N的非负整数序列 Ai ,对于所有 1 ≤ k ≤ (N + 1) / 2 ,
输出 A1,A3,…,A2k - 1的中位数。即前 1,3,5,… 个数的中位数。
输入输出格式
输入格式:第1行为一个正整数 N,表示了序列长度。
第2行包含N个非负整数 Ai (Ai ≤ 10^9) 。
输出格式:
共 (N + 1) / 2 行,第 i 行为 A1,A3,…,A2k - 1 的中位数。
输入输出样例
说明
对于 20% 的数据,N ≤ 100;
对于 40% 的数据,N ≤ 3000;
对于 100% 的数据,N ≤ 100000。
这道题乍一看比较简单 其实还是有不少细节的
我第一想法是建立主席树 然后每次插数的时候就查询中位数个数那么大的数就好了
是查询区间第k大的板子嘛 时间复杂度是 $O(nlogn)$
然而这道题O(nlogn2)可以很容易的搞过去 所以在wans的怂恿下写了值域树状数组套二分...写完才发现并不是那么容易的
第一个问题就是数据是 $ 1e9 $ 范围 显然需要离散化 然后离散化完了还要映射回去
然后就是二分的问题 每次二分查找前面的数的个数 如果前面的数的个数包括自己刚好等于中位数的个数
就先更新答案 然后 r=mid-1 因为有可能我找到了一个空值 就比如 $10101000$ 而我找到的是后面的 直接返回就会出问题
第二个是当我找到的值大于我中位数的位数时 一样需要更新答案 因为我一个数可能出现多次 我找到的是大于中位数排名的个数
但是这时候这个排名可能恰好处于这个数所在的区间 但是因为我们计算加上了整个区间的个数 所以这时候也要更新答案
还有就是 $unique$ 记得 - 1 !!!!!!
代码
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 5; int n,m,c[N],val[N],a[N],b[N],ma; int lowbit(int pos) { return pos & (-pos); } void add(int pos,int del) { while(pos <= m) { c[pos] += del; pos += lowbit(pos); } } int query(int pos) { int ans = 0; while(pos >= 1) { ans += c[pos]; pos -= lowbit(pos); } return ans; } void solve(int num,int pos) { int l = 1,r = pos,ans = num; while(l <= r) { int mid = (l + r) >> 1; if(query(mid) == num) { ans = mid; r = mid - 1; } else if(query(mid) < num) l = mid + 1; else if(query(mid) > num) ans = mid,r = mid - 1; } printf("%d\n",val[ans]); } int main( ) { scanf("%d",& n); for(int i = 1;i <= n;i ++) { scanf("%d",& a[i]); b[i] = a[i]; } sort(a + 1,a + n + 1); m = unique(a + 1,a + n + 1) - a - 1; for(int i = 1;i <= n;i ++) { int pos = lower_bound(a + 1,a + m + 1,b[i]) - a; val[pos] = b[i]; } for(int i = 1;i <= n;i ++) { int pos = lower_bound(a + 1,a + m + 1,b[i]) - a; add(pos,1); int nn = query(m); if(i % 2 == 1) solve((nn + 1) / 2,m); } }