HDU 2852 KiKi's K-Number(动态过程求第K小数)

题意:

给出三种操作,

0 在容器中插入一个数。

1 在容器中删除一个数。

2 求出容器中大于a的第k大元素。

思路:可以用树状数组和线段树,显然a[1]+...+a[i]随i有明显的单调性,所以可以二分出答案

线段树时间复杂度比树状数组的常数大了几倍...所以线段树擦边过了

还有另外一种思路:二分只是二分出a[1]+...+a[i]的上界i,所以可以逆向考虑,从a[1]开始累加,直到到达k,这样的复杂度就由原来的

O(lgN*lgN) 变成O(lgN)了。难在累加的过程,线段树和树状数组是同样的累加方法详见代码

线段树二分代码:

12989210 2015-02-25 20:49:32 Accepted 2852 1965MS 4176K 2321 B C++ ka

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int M = 100010;
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
struct node
{
    int l,r;
    int sum;
}tree[M<<2];
int ans = 0;
void build(int l,int r,int rt)//建树
{
    tree[rt].l =l;
    tree[rt].r =r;
    tree[rt].sum =0;
    if(l ==r ) return ;
    int m=(l+r)>>1;

    build(lson);
    build(rson);
}
void up(int rt){
    tree[rt].sum=tree[rt<<1].sum+tree[rt<<1|1].sum;
}

void updata(int l,int r,int rt,int pos,int j)
{
    if(l==r)  {
        tree[rt].sum +=j;
        return;
    }

    int m=(tree[rt].l +tree[rt].r )/2;
    if(pos<=m)
        updata(lson,pos,j);
    else
        updata(rson,pos,j);
    up(rt);
}
int query(int rt,int L,int R)
{
    int m=(tree[rt].l +tree[rt].r)>>1;
    if(L<=tree[rt].l&&tree[rt].r <=R)
    {
        return tree[rt].sum ;
    }
    int ans=0;
    if(L<=m)
        ans+=query(rt<<1,L,R);
    if(R>m)
        ans+=query(rt<<1|1,L,R);
    return ans;
}

int B_search(int k)
{
    int lb = 1;
    int ub = M-1;
    int w=-1;
    while(lb<=ub)
    {
        int m = (lb + ub)>>1;
        ans=query(1,1,m);
        if(ans>=k)
        {
            ub = m - 1;
            w=m;
        }
        else
        {
            lb = m+1;
        }
    }
    return w;
}
int main()
{
    int n,a,b,k;
    while(~scanf("%d",&n))
    {
        build(1,M,1);
        for(int i = 0;i<n;i++)
        {
            scanf("%d",&a);
            if(a==0)
            {
                scanf("%d",&b);
                updata(1,M,1,b,1);
            }
            else if(a==1)
            {
                scanf("%d",&b);
                ans=query(1,b,b);
                if(!ans)
                    puts("No Elment!");
                else
                    updata(1,M,1,b,-1);
            }
            else if(a==2)
            {
                scanf("%d%d",&b,&k);
                ans=query(1,1,b);
                int tmp = B_search(ans+k);
                if(tmp==-1)
                    puts("Not Find!");
                else
                    printf("%d\n",tmp);
            }
        }
    }
    return 0;
}

树状数组二分代码:(可见比线段树快了很多)

//468MS 1484K
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
#define M 100100
#define lowbit(x) (x&-x)
int m;
int C[M];
void update(int rt,int val){
    for(int i=rt;i<M;i+=lowbit(i)){
        C[i]+=val;
    }
}
int sum(int rt){
    int s=0;
    for(int i=rt;i>0;i-=lowbit(i)){
        s+=C[i];
    }
    return s;
}

int main(){
    while(~scanf("%d",&m)){
        int op;
        memset(C,0,sizeof(C));
        while(m--){
            scanf("%d",&op);
            if(op==0){
                int x;
                scanf("%d",&x);
                update(x,1);
            }
            else if(op==1){
                int x;
                scanf("%d",&x);
                if(sum(x)-sum(x-1)==0){
                    printf("No Elment!\n");
                    continue;
                }
                update(x,-1);
            }
            else {
                int x,k;
                scanf("%d%d",&x,&k);
                k+=sum(x);
                int lb=1,ub=M-1;
                while(ub>=lb){
                    int mid=(lb+ub)>>1;
                    if(sum(mid)>=k) ub=mid-1;
                    else lb=mid+1;
                }

                if(lb==M) printf("Not Find!\n");
                else printf("%d\n",lb);
            }
        }
    }
    return 0;
}

逆向累加:(树状数组的累加是二进制的操作,可还原树形图来便于理解)

http://www.cnblogs.com/wuyiqi/archive/2011/12/25/2301071.html 这篇文章有解释

这样果然快了不少,跑到第11名

11 ka 312MS 1484K 1420B  

//312MS 1484K
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
#define M 100100
#define lowbit(x) (x&-x)
int m;
int C[M];
void update(int rt,int val){
    for(int i=rt;i<M;i+=lowbit(i)){
        C[i]+=val;
    }
}
int sum(int rt){
    int s=0;
    for(int i=rt;i>0;i-=lowbit(i)){
        s+=C[i];
    }
    return s;
}
int find_kth(int k){
    int ans=0,s=0;
    for(int i=20;i>=0;i--){
        ans+=(1<<i);
        if(ans>=M||s+C[ans]>=k){
            ans-=(1<<i);
        }
        else s+=C[ans];
    }
    return ++ans;
}
int main(){
    while(~scanf("%d",&m)){
        int op;
        memset(C,0,sizeof(C));
        while(m--){
            scanf("%d",&op);
            if(op==0){
                int x;
                scanf("%d",&x);
                update(x,1);
            }
            else if(op==1){
                int x;
                scanf("%d",&x);
                if(sum(x)-sum(x-1)==0){
                    printf("No Elment!\n");
                    continue;
                }
                update(x,-1);
            }
            else {
                int x,k;
                scanf("%d%d",&x,&k);
                k+=sum(x);
                int ans=find_kth(k);
                if(ans==M) printf("Not Find!\n");
                else printf("%d\n",ans);
            }
        }
    }
    return 0;
}

线段树的逆向累加方法也比二分快了不少:

//655MS 2660K
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define M 100100

#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1

int tree[M<<2];
bool flag;

void up(int rt){
    tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
bool update(int pos,int val,int l,int r,int rt){
    flag=true;
    if(l==r){
        if(val==-1&&tree[rt]==0){
            flag=false;
            return false;
        }
        tree[rt]+=val;
        return true;
    }
    int m=(l+r)>>1;
    if(pos<=m) update(pos,val,lson);
    else update(pos,val,rson);
    up(rt);
    return flag? true:false;
}
int query(int L,int R,int l,int r,int rt){
    if(L<=l&&r<=R){
        return tree[rt];
    }
    int m=(l+r)>>1;
    int ans=0;
    if(L<=m) ans+=query(L,R,lson);
    if(R>m) ans+=query(L,R,rson);
    return ans;
}
void find_kth(int l,int r,int rt,int k)
{
    if(l==r)
    {
        printf("%d\n",l);
        return ;
    }
    int m=(l+r)>>1;
    if(k<=tree[rt<<1]) find_kth(lson,k);
    else find_kth(rson,k-tree[rt<<1]);
}
int main(){
    int m;
    while(~scanf("%d",&m)){
        memset(tree,0,sizeof(tree));
        while(m--){
            int op;
            scanf("%d",&op);
            if(op==0){
                int x;
                scanf("%d",&x);
                update(x,1,1,M-1,1);
            }
            else if(op==1){
                int x;
                scanf("%d",&x);
                if(update(x,-1,1,M-1,1)==false) printf("No Elment!\n");
            }
            else {
                int a,k;
                scanf("%d%d",&a,&k);
                k+=query(1,a,1,M-1,1);
                if(k>tree[1]){
                    printf("Not Find!\n");
                    continue;
                }
                find_kth(1,M-1,1,k);
            }
        }
    }
    return 0;
}

HDU 2852 KiKi's K-Number(动态过程求第K小数)

时间: 2024-07-31 14:33:10

HDU 2852 KiKi's K-Number(动态过程求第K小数)的相关文章

hdu 2852 KiKi&#39;s K-Number

L - KiKi's K-Number Time Limit:2000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit Status Description For the k-th number, we all should be very familiar with it. Of course,to kiki it is also simple. Now Kiki meets a very similar

HDU 2852 KiKi&#39;s K-Number(线段树+树状数组)

KiKi's K-Number Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 2603    Accepted Submission(s): 1202 Problem Description For the k-th number, we all should be very familiar with it. Of course,t

HDU 2852 KiKi&#39;s K-Number(树状数组+二分)

KiKi's K-Number Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 2598    Accepted Submission(s): 1199 Problem Description For the k-th number, we all should be very familiar with it. Of course,t

HDU 2852 KiKi&#39;s K-Number (树状数组 &amp;&amp; 二分)

题意:给出对容器的总操作次数n, 接下来是这n个操作.这里对于一个容器提供三种操作, 分别是插入.删除和查找.输入0  e表示插入e.输入1  e表示删除e,若元素不存在输出No Elment!.输入2  e  k表示查找比e大且第k大的数, 若不存在则输出Not Find! 分析:这里考虑树状数组做的原因是在第三个操作的时候, 只要我们记录了元素的总数, 那通过求和操作, 便能够高效地知道到底有多少个数比现在求和的这个数要大, 例如 tot - sum(3)就能知道整个集合里面比3大的数到底有

hdu 2852 KiKi&#39;s K-Number (线段树)

hdu 2852 题意: 一个容器,三种操作: (1) 加入一个数 e (2) 删除一个数 e,如果不存在则输出 No Elment! (3) 查询比a大的数中的第k小数,不存在就输出 Not Find! 解法: 关于第三点,可以先查询小于等于a的数的个数cnt,然后直接查询第cnt+k小数就行了 . 二分+树状数组 或者 主席树(有点杀鸡用牛刀的感觉 ...) 也是可以做的  _(:з」∠)_ code:线段树 1 #include <iostream> 2 #include <cst

HDU 2852 KiKi&#39;s K-Number【 树状数组 二分 】

题意:给出m个操作,0:是增加一个数,add(x,1)1:是删除一个指定的数,这个是看sum(x) - sum(x-1)是否为0,为0的话则不存在,不为0的话,则add(x,-1)2:是查询比x大的数中第k大的数,先求出比x小的个数s,假设比x大的数中第k大的数为y,那么比y小的个数有s+k个二分y的值来找 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include <cmath&g

hdu2852 KiKi&#39;s K-Number 树状数组求第k大数

//再求第k大数时只需要getsum(b-1)<getsum(a)+k<=getsum(b) //b就是a的第k大数 //又gesum(b-1)<=getsum(b)则可以用二分查找来做 #include<iostream> #include<cstdio> #include<cstring> using namespace std; const int maxn=100010; int tree[maxn]; int lowbit(int i) {

zoj 2112 动态区间求第k大

题目大意: 动态单点更新,然后多次询问求区间内第k大 这里单个的主席树不能实现,这里采取的是树状数组套主席树 首先可以想的是将静态主席树先构建好,不去动它,这里空间复杂度就是O(nlogn),这个只要之前做过主席树的入门题的话就都不是问题 然后考虑更新的情况,这里将更新产生的前缀变化保存在树状数组中,那么每次更新都要更新logn棵树状数组上的主席树,每一棵的更新操作都是 logn次的,那么时间复杂度就是nlognlogn的只是可以承受的 之后的询问也是,预处理好用到的树状数组,然后保存到向量中,

hdu 2852(树状数组+二分)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2852 KiKi's K-Number Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 2668    Accepted Submission(s): 1227 Problem Description For the k-th number,