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

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里面;
对于一个数吧,有三种情况

  1. 没出现过这个数
  2. 这个数恰好有一个
  3. 这个数有很多

因为个数是奇数,所以我们枚举到一个空数时,比他小的个数必然不等于比他大的,可以继续二分;
然后恰好有一个,我们直接找到有几个比他小,如果是总数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);
    }
}
posted @ 2017-02-23 18:43  largecube233  阅读(333)  评论(0编辑  收藏  举报