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;
    }
  • 相关阅读:
    LeetCode(111) Minimum Depth of Binary Tree
    LeetCode(108) Convert Sorted Array to Binary Search Tree
    LeetCode(106) Construct Binary Tree from Inorder and Postorder Traversal
    LeetCode(105) Construct Binary Tree from Preorder and Inorder Traversal
    LeetCode(99) Recover Binary Search Tree
    【Android】通过经纬度查询城市信息
    【Android】自定义View
    【OpenStack Cinder】Cinder安装时遇到的一些坑
    【积淀】半夜突然有点想法
    【Android】 HttpClient 发送REST请求
  • 原文地址:https://www.cnblogs.com/6262369sss/p/10744876.html
Copyright © 2011-2022 走看看