区间求小于等于k的数字个数 hdu4177

我的未来我决定 提交于 2020-04-30 04:21:34

题目链接: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:\n",++Case);
        while(m--){
            int l,r,k;
            scanf("%d%d%d",&l,&r,&k);
            printf("%d\n",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:\n",++Case);
        while(m--){
            scanf("%d%d%d",&l,&r,&k);
            l++;
            r++;
            int index=binary(1,n,k);//查找序列里面最后一个小于等于k的数字的位置,对应于线段树的节点位置,因为都是排序之后的相对大小 
            printf("%d\n",ask(rt[l-1],rt[r],1,index,1,n));
        }
    }
    return 0;
}

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!