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

来源:互联网 发布:redis mac 安装配置 编辑:程序博客网 时间:2024/06/12 03:38

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);    }}
0 0
原创粉丝点击