您的位置:首页 > 其它

二分树状数组-洛谷P1168 中位数

2017-02-23 18:43 274 查看
https://www.luogu.org/problem/show?pid=1168

本来和fopzz想练练树状数组,就搜索到了这道题;

TM二分的条件搞了一个下午;

基本思想就是二分寻找答案,然后用树状数组去维护有几个比这个二分出来的值大,然后就没有了;

数据要离散,这个好像用map也可以,但是不推荐;

我一开始TLE然后就以为是map慢,结果改好后是降了600ms;

那怎么离散呢?

我们先把a数组读入并复制给b数组;

然后排序a;

这个时候a数组就有序了,我们就可以把b数组里的值通过二分找到其在a数组里的下标,这样就把1~1e9的数据压缩到1e5了;

这样的离散支持去重,支持不去重;

离散好了我们咋么搞呢?

树状数组怎么来维护比x小的数的个数呢?;

我们能用树状数组来维护前=前缀和;

那我们每增加一个数,我们就把他当作下标,在上面+1;

然后我统计小于等于x的个数时直接取x的前缀和好了;

对于二分求答案,从1~n里二分;

因为离散后数据一定再1~n里面;

对于一个数吧,有三种情况

没出现过这个数

这个数恰好有一个

这个数有很多

因为个数是奇数,所以我们枚举到一个空数时,比他小的个数必然不等于比他大的,可以继续二分;

然后恰好有一个,我们直接找到有几个比他小,如果是总数div2就是答案了;

如果有多个,设数是x,那显然 <=x的 减 小于x的 大于1;

这时如果<=x大于总数div2,小于x的小于总数div2,答案就是x;

#include<cstdio>//cfb
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<map>
using namespace std;
int a[100001],b[100001],c[100001];
bool vi[100001];
int n,ans,nn,x;
void add(int x,int y){
for(int i=x;i<=n;i+=i&-i)c[i]+=y;
}
int out(int x){
int ans=0;for(int i=x;i;i-=i&-i)ans+=c[i];return ans;
}
void outit(int num){
int l=1,r=n,ans;
while(r>=l){
int mid=l+r>>1;
int sum=out(mid-1);
int s=out(mid);//为什么这里是>而不是>=,因为s包含答案自己,要-1
if(vi[mid]&&((sum<num&&s>num)||sum==num)){ans=mid;break;}
if(sum>num)r=mid-1;else l=mid+1;
}
printf("\n%d",a[ans]);
}
int er(int x){
int l=1,r=n,ans;
while(1){
int mid=l+r>>1;
if(a[mid]==x)return mid;
if(a[mid]>x)r=mid-1;else l=mid+1;
}
}
int main(){
scanf("%d",&n);
if(!(n&1))n--;
for(int i=1;i<=n;i++)scanf("%d",&a[i]),b[i]=a[i];x=a[1];
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)b[i]=er(b[i]);
printf("%d",x);
add(b[1],1);vi[b[1]]=1;
for(int i=3;i<=n;i+=2){
add(b[i-1],1);
add(b[i],1);
vi[b[i-1]]=vi[b[i]]=1;
outit(i>>1);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: