poj_2985 The k-th Largest Group 树状数组求第K大

来源:互联网 发布:卡尔曼滤波原理和算法 编辑:程序博客网 时间:2024/05/22 00:07

题目链接:http://poj.org/problem?id=2985

题目大意:某人养了很多猫,他会把一些猫合并成一组,并且会询问第k大组有几只猫

前面说了树状数组的三个用途,现在说一下树状数组求  第K大。

对于一个数组,求第K大的数,只需要sort排序一下,取第k个值即可,但是当不停的改变数组中元素时(即在线询问),对于每次询问的第k大值,都要对数组重新进行排序,时间复杂度(快排N*logN)*(询问N);

那我们来看一下用树状数组:

首先树状数组C【i】里面存的是在i管辖的范围内各个组数的和,比如  1出现2次,2出现3次,4出现6次,那么a【1】=2,a【2】=3,a【3】=0,a【4】=6;故c【4】=11;

对于a数组{1,1,2,2,2,2,3,4,5,5,5,5,6,7,8};

则现在的a数组为{2,4,1,1,4,1,1,1},有这个数组,树状数组应该自己能构建出来了吧。。。

第一种方法:二分法

每次二分产生一个mid,判断get_sum(mid)的值与k的大小,更新mid的值,代码如下:

#include<stdio.h>#define N 200005int m,n;int f[N],a[N],c[N];void init()//初始化{    for(int i=1;i<N;i++)    {        f[i]=i;        a[i]=1;//初始状态每种编号的猫只有一只    }}int find(int x)//判断两堆猫是否在一个集合{   if(x==f[x]) return x;   return f[x]=find(f[x]);}int lowbit(int x)  {return x&(-x);}void insert(int x,int v)//更改某种数量猫的个数{    while(x<N)    {        c[x]+=v;        x+=lowbit(x);    }}int get_sum(int x)//求和{    int sum=0;    while(x>0)    {        sum+=c[x];        x-=lowbit(x);    }    return sum;}int main(){    int m,n;    while(~scanf("%d%d",&n,&m))    {        init();        insert(1,n);//个数为1的堆数有N堆,故在1处插入N,向后更新        int num=n;        for(int i=1;i<=m;i++)        {            int mark;            scanf("%d",&mark);            if(mark==0)            {                int x,y;                scanf("%d%d",&x,&y);                int fx=find(x);                int fy=find(y);                if(fx==fy) continue;                insert(a[fx],-1);//当fx和fy合并后,数量为fx和fy的堆数都减少1                insert(a[fy],-1);                a[fy]+=a[fx];                insert(a[fy],1);//数量为fx+fy的堆数增加1                f[fx]=fy;                num--;//没次合并总堆数-1            }            else            {                int k;                scanf("%d",&k);                k=num-k+1;//第k大即第num-k+1小                int s=1,e=n,mid;                while(s<=e)                {                    mid=(s+e)/2;                    if(get_sum(mid)>=k)//尽量向左逼近                        e=mid-1;                    else                        s=mid+1;                }                printf("%d\n",s);            }        }    }    return 0;}

上面的方法还比较好理解,下面说一下第二种方法:

首先说一个东西:任何一个正整数都能分成几个数的平方的和,然后就有了下面的方法:

前k大的数我们假如有x个,那么我们就先找到一个比x小的最大的数y,然后x=x-y,然后再找一个y,不停的找下去,直到x为1 。。。

下面解释摘自大牛博客:

因为要求第k小的数,所以要从左往右加过去,
上述过程其实就是把树状数组的求和操作逆向,从左往右求和,
边求和边判断控制范围内比当前值要小的数是否超过或等于k,如果是则跳回兄弟节点(ans-=(1<<i))
如8+4=12,假如12不满足要求,则重新变回8,下一次就加2,8+2=10,即跳到10控制的位置
上述累加过程不会重复计算,因为
比如15=8+4+2+1,数字依次为8  12  14  15,每次累加后的值都与前面的值无关,i小于其二进制末尾0的个数
即c[8] 、c[12] 、c[14]、 c[15]相加的话不会重复计算,再如11=8+2+1;数字依次为8 10 11,c[8],c[10],c[11]
各自控制着自己的范围,不会重复累加,所以就可以用cnt来累加前面的结果,最后cnt+c[ans]就表示了值<=ans的个数
简言之:上述的各个数字两两间控制的范围不会相交

代码如下:

#include<stdio.h>#define N 200005int m,n;int f[N],a[N],c[N];void init(){    for(int i=1;i<N;i++)    {        f[i]=i;        a[i]=1;    }}int find(int x){   if(x==f[x]) return x;   return f[x]=find(f[x]);}int lowbit(int x)  {return x&(-x);}void insert(int x,int v){    while(x<N)    {        c[x]+=v;        x+=lowbit(x);    }}int get_sum(int x){    int sum=0;    while(x>0)    {        sum+=c[x];        x-=lowbit(x);    }    return sum;}int find_kth(int k){    int ans=0,cnt=0;    for(int i=20;i>=0;i--)//1<<20应该不小了吧    {        ans+=(1<<i);        if(ans>N||cnt+c[ans]>=k)            //这里大于等于k的原因是可能大小为ans的数不在c[ans]的控制范围之内,所以这里求的是 < k            ans-=(1<<i);        else          //cnt用来累加比当前ans小的总组数            cnt+=c[ans];    }    //求出的ans是累加和(即小于等于ans的数的个数)小于k的情况下ans的最大值,所以ans+1就是第k大的数    return ans+1;}//因为要求第k小的数,所以要从左往右加过去,//上述过程其实就是把树状数组的求和操作逆向,从左往右求和,//边求和边判断控制范围内比当前值要小的数是否超过或等于k,如果是则跳回兄弟节点(ans-=(1<<i))//如8+4=12,假如12不满足要求,则重新变回8,下一次就加2,8+2=10,即跳到10控制的位置//上述累加过程不会重复计算,因为//比如15=8+4+2+1,数字依次为8  12  14  15,每次累加后的值都与前面的值无关,i小于其二进制末尾0的个数//即c[8] 、c[12] 、c[14]、 c[15]相加的话不会重复计算,再如11=8+2+1;数字依次为8 10 11,c[8],c[10],c[11]//各自控制着自己的范围,不会重复累加,所以就可以用cnt来累加前面的结果,最后cnt+c[ans]就表示了值<=ans的个数//简言之:上述的各个数字两两间控制的范围不会相交int main(){    int m,n;    while(~scanf("%d%d",&n,&m))    {        init();        insert(1,n);        int num=n;        for(int i=1;i<=m;i++)        {            int mark;            scanf("%d",&mark);            if(mark==0)            {                int x,y;                scanf("%d%d",&x,&y);                int fx=find(x);                int fy=find(y);                if(fx==fy) continue;                insert(a[fx],-1);                insert(a[fy],-1);                a[fy]+=a[fx];                insert(a[fy],1);                f[fx]=fy;                num--;            }            else            {                int k;                scanf("%d",&k);                k=num-k+1;                printf("%d\n",find_kth(k));            }        }    }    return 0;}

品味。。。

0 0
原创粉丝点击