zoukankan      html  css  js  c++  java
  • 区间求小于等于k的数字个数 hdu4177

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4417

    题目意思给出一个序列,叫我们求一个区间里面小于等于k的数字个数。

    这里面我用分块和主席树两种方法都做了一遍,恩,主席树虽然费空间,但是还是比分块块很多的。

    因为每个人的风格不一样,所以我的代码可能比较长,比较繁琐。

    首先是分块,分块的思想就是把整个区间划分成多个块,用数组来记录每个块的信息,当我们对一个区间进行查询或者修改的时候,一般来说就会有一些块完全的在这个区间里面,对于这种块被区间完全包含的情况,我们就可以对这些块进行整体的操作,把每一块看成一个整体来进行查询或者修改,而对于那种不完整的块(这种块一般就在区间的两头,最多就只有两块是没有完全被包含在区间里面的,其实左右两边的块就算是完整的我们也把它看成不完整的块来处理),我们对它进行暴力修改或者查询。

    在这道题目里面,我们把给出的初始序列分块,假设我用a数组来储存,那么我把a数组复制给b数组,对b数组的每一个块进行块内排序,这样当我们查询区间小于等于k的的数字个数时,对于完整的块我们就可以在b数组里面用二分在块内进行查找(b数组是有序的),对于不完整的块,我们将在原来的数组a里面用暴力查找。

    代码:

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    #include<map>
    #include<stack>
    #include<cmath>
    #include<vector>
    #include<set>
    #include<cstdio>
    #include<string>
    #include<deque> 
    using namespace std;
    typedef long long LL;
    #define eps 1e-8
    #define INF 0x3f3f3f3f
    #define maxn 100005
    int n,m,t,num;
    int a[maxn],b[maxn];
    int block,zu[maxn];//zu[i]表示第i个数字在第几块 
    void init(){
        block=(int)sqrt(n);
        num=-1;
        for(int i=1;i<=n;i++){
            zu[i]=(i-1)/block+1;
            num=max(num,zu[i]);
        }
    }
    int ask(int l,int r,int k){
        int ans=0;
        for(int i=l;i<=min(r,zu[l]*block);i++){//左边不完整的块暴力查找 
            if(a[i]<=k)
            ans++;
        }
        for(int i=zu[l]+1;i<=zu[r]-1;i++){//中间完整的块二分查找 
            int L=(i-1)*block+1;//块内左边界 
            int R=i*block;        //块内右边界 
            ans+=upper_bound(b+L,b+R+1,k)-(b+L);//找块内第一个大于k的数字 
        }
        if(zu[l]!=zu[r]){//判断左边不完整的块和右边不完整的块是不是同一个块 
            for(int i=(zu[r]-1)*block+1;i<=r;i++){
                if(a[i]<=k)
                ans++;
            }
        }
        return ans;
    } 
    int main()
    {
        int Case=0;
        scanf("%d",&t);
        while(t--){
            scanf("%d%d",&n,&m);
            init();
            for(int i=1;i<=n;i++){
                scanf("%d",&a[i]);
                b[i]=a[i];
            }
            for(int i=1;i<=num;i++){
                int l=block*(i-1)+1;//当前块的左边界 
                int r=min(block*i,n);//当前块的右边界 
                sort(b+l,b+r+1);//块内排序 
            }
            printf("Case %d:
    ",++Case);
            while(m--){
                int l,r,k;
                scanf("%d%d%d",&l,&r,&k);
                printf("%d
    ",ask(l+1,r+1,k));//下标加一,因为我是从下标1开始的 
            }
        }
        return 0;
    }

    然后就是主席树,我也是刚刚学,如果不会可以看这篇博客:

    https://blog.csdn.net/bestFy/article/details/78650360

    主席树可以用来求区间第k大,也可以用来求区间里面小于等于k的数字个数(发现新姿势),我的思路可能绕了圈圈,我们对给出的序列进行离散化,这样就得到了序列里面每个数字的相对大小(我们对每一个数字进行编号,编号为1的数字就是区间里面最大的数字),然后在建n课线段树,线段树的每一个节点代表着序列里面每一个数字的编号,从左往右编号依次增加,也就是说从左往右依次是第1大,第二大......第n大的数字;第 i 颗线段树就代表着原始序列里从1到i这段前缀中所有编号出现的情况(每一个编号的数字在这一段编号里面出现过没,出现了几次),这样我们建成的线段树就可以相互之间进行相减,和前缀和差不多,第5颗线段树的每一个节点减去第2颗线段树的每一个节点就是[3,5]这个区间的信息。可能不清楚,看上面博客好吧。

    求区间比k大的数字的个数,我们可以在排序之后的序列里面找小于等于k的最后一个数字对应的位置,假设是index,那么线段树叶子中从1到index里所有叶子节点所对应的数字就是全部小于等于k了,这里面的都是相对大小,假如给出的区间是L和R,那么我们最后查询时就是查询第R颗线段树减去第L颗线段树,得到的一棵虚拟的数里面在区间[1,index]里面的和。

    代码:

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    #include<map>
    #include<stack>
    #include<cmath>
    #include<vector>
    #include<set>
    #include<cstdio>
    #include<string>
    #include<deque> 
    using namespace std;
    typedef long long LL;
    #define eps 1e-8
    #define INF 0x3f3f3f3f
    #define maxn 100005
    struct node{
        int l,r,sum;
    }tree[maxn*25];
    int n,m,k,t,cnt;
    struct point{
        int id,w;
    }a[maxn];
    int b[maxn],rt[maxn];
    bool operator <(point s1,point s2){
        if(s1.w!=s2.w)
        return s1.w<s2.w;
        else
        return s1.id<s2.id;
    }
    void update(int root){
        tree[root].sum=tree[tree[root].l].sum+tree[tree[root].r].sum;
    }
    void build_0(int &root,int l,int r){
        root=++cnt;
        tree[root].l=l;
        tree[root].r=r;
        tree[root].sum=0;
        if(l==r)
        return;
        int mid=(l+r)/2;
        build_0(tree[root].l,l,mid);
        build_0(tree[root].r,mid+1,r);
        update(root); 
    }
    void build(int pre,int &root,int l,int r,int index){
        root=++cnt;
        tree[root]=tree[pre];
        if(l==r){
            tree[root].sum++;
            return;
        }
        int mid=(l+r)/2;
        if(index<=mid)
        build(tree[pre].l,tree[root].l,l,mid,index);
        else
        build(tree[pre].r,tree[root].r,mid+1,r,index);
        update(root);
    }
    int binary(int l,int r,int k){//二分查找区间里面小于等于k的最后一个数字所在的位置 
        while(l<=r){
            int mid=(l+r)/2;
            if(a[mid].w>k)
            r=mid-1;
            else
            l=mid+1;
        }
        return r;
    }
    int ask(int root1,int root2,int L,int R,int l,int r){
        if(L>R)
        return 0; 
        if(l>=L&&r<=R){
            return tree[root2].sum-tree[root1].sum;
        }
        int mid=(l+r)/2;
        int ans=0;
        if(mid>=L)
        ans+=ask(tree[root1].l,tree[root2].l,L,R,l,mid);
        if(mid<R)
        ans+=ask(tree[root1].r,tree[root2].r,L,R,mid+1,r);
        return ans;
    }
    int main()
    {
        scanf("%d",&t);
        int Case=0;
        while(t--){
            scanf("%d%d",&n,&m);
            cnt=0;
            for(int i=1;i<=n;i++){
                scanf("%d",&a[i].w);
                a[i].id=i;
            }
            //离散化,我的离散化结果是没有重复的编号,这里其实有点多余
            sort(a+1,a+n+1); 
            for(int i=1;i<=n;i++){
                b[a[i].id]=i;
            }
            build_0(rt[0],1,n);//建第0颗树 
            for(int i=1;i<=n;i++)
            build(rt[i-1],rt[i],1,n,b[i]);//建第i颗树 
            int l,r,k;
            printf("Case %d:
    ",++Case);
            while(m--){
                scanf("%d%d%d",&l,&r,&k);
                l++;
                r++;
                int index=binary(1,n,k);//查找序列里面最后一个小于等于k的数字的位置,对应于线段树的节点位置,因为都是排序之后的相对大小 
                printf("%d
    ",ask(rt[l-1],rt[r],1,index,1,n));
            }
        }
        return 0;
    }
  • 相关阅读:
    root用户Linux 环境变量的配置解决(-bash: jps: command not found)有关问题
    Linux Crontab内环境变量与Shell环境变量的关系及解决问题的办法
    RocketMQ os.sh 系统优化(CentOS)
    Spring Boot修改内置Tomcat端口号
    SpringBoot多跨域请求的支持(JSONP)
    [译]Spring Boot 构建一个RESTful Web服务
    delphi怎样把子窗体显示在pagecontrol的tabsheet
    delphi从TRichEdit获得RTF格式文本(PC版本)
    数据类型表(DELPHI、C++)
    对程序进行注释
  • 原文地址:https://www.cnblogs.com/6262369sss/p/10744876.html
Copyright © 2011-2022 走看看