求区间不同数的个数

Posted bennian

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了求区间不同数的个数相关的知识,希望对你有一定的参考价值。

题目:https://www.nowcoder.com/acm/contest/139/J
题意:给出n个数,求 [1,L],[R,n]这两个区间不同数的个数
其实你只要把区间扩大一倍,就是求 [R,L+n]这个区间了

求区间内不同数的个数解决方法有很多

像用离线树状数组、离线莫队、线段树、主席树等等

不过听说主席树会TLE,所以这里主要说一下树状数组和莫队算法
1.树状数组(BIT)
其实可以用树状数组就一定能用线段树,因为树状数组就是从线段树中演变来的,
只不过BIT写起来更方便,其复杂度跟线段树一样,都是 O(log n)
它主要进行区间求和和区间内某个值的加减
现在我们回到题目,求一段区间不同数的个数,做法就是先离线按照R从小到大排序,
然后用map或者一个标记数组,一直只记录每个重复值最后一次出现的下标,用BIT
在这个位置置1,每次更新就-1,一直维护重复值的最后一个的下标+1,然后每次用

BIT去求和就行了

代码如下:

#include<bits/stdc++.h>
using namespace std;
#define MAX 200005
int bit[MAX],n,q;
map<int,int>mp;
struct Query
{
    int l,r,id;
    bool operator < (const Query& a)const{
      return r<a.r;
    }
}b[MAX];
int gsum(int i)
{
    int s=0;
    while(i>0)
    {
        s+=bit[i];
        i-=i&-i;
    }
    return s;
}
void add(int i,int k)
{
    while(i<=n)
    {
        bit[i]+=k;
        i+=i&-i;
    }
}
int main()
{
    while(~scanf("%d %d",&n,&q)){
      mp.clear();
    memset(bit,0,sizeof(bit));
    vector<int>a(n*2+10),ans(q);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        a[i+n]=a[i];
    }
    n=n*2;
    for(int i=0;i<q;i++)
    {
        int l,r;
        scanf("%d %d",&l,&r);
        b[i].l=r;
        b[i].r=l+n/2;
        b[i].id=i;
    }
    sort(b,b+q);
    int pre=1;
    for(int i=0;i<q;i++)
    {
        for(int j=pre;j<=b[i].r;j++)
        {
            if(mp[a[j]])
            {
                add(mp[a[j]],-1);
            }
            add(j,1);
            mp[a[j]]=j;
        }
        pre=b[i].r+1;
      ans[b[i].id]=gsum(b[i].r)-gsum(b[i].l-1);
    }
    for(int i=0;i<q;i++)
    {
        printf("%d\\n",ans[i]);
    }
  }
}

2、莫队算法

莫队算法这个就是从枚举暴力中优化来的,其复杂度是O(n^1.5);

前提是:如果我们已知[l,r]的答案,能在O(1)时间得到[l+1,r]的答案以及[l,r-1]的答案,

即可使用莫队算法。

给几个链接看看:http://www.cnblogs.com/hzf-sbit/p/4056874.html

http://ydcydcy1.blog.163.com/blog/static/21608904020134411543898/

其中的精华就是分块,用一个block数组将元素分成根号n块,

即:for(int i=1;i<=n;i++)

          block[i]=i/sqrt(n);

然后是排序,在本题中就是:

bool cmp(const Query&a,const Query&b)
{
    if(block[a.l]==block[b.l])
        return a.r<b.r;
    else return a.l<b.l;
}

最后就是从[L,R]推出[L,R+1]或者[L+1,R]

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
#define MAX 100005
int block[MAX],ans[MAX],n,b[MAX];
int sum=0;
int read()
{
    char ch=\' \';
    int ans=0;
    while(ch>\'9\'||ch<\'0\')
        ch=getchar();
    while(ch>=\'0\'&&ch<=\'9\')
    {
        ans=ans*10+ch-\'0\';
        ch=getchar();
    }
    return ans;
}
struct Query
{
    int l,r,id;
}s[MAX];
bool cmp(const Query&a,const Query&b)
{
    if(block[a.l]==block[b.l])
        return a.r<b.r;
    else return a.l<b.l;
}
void inc(int x)
{
   if(b[x]==0)sum++;
   b[x]++;
}
void dec(int x)
{
    b[x]--;
    if(b[x]==0)sum--;
}
int main()
{
    int q;
    while(~scanf("%d %d",&n,&q))
    {
        memset(b,0,sizeof(b));
        vector<int>a(n);
        for(int i=1;i<=n;i++)
        {
             a[i]=read();
            block[i]=i/1000;
        }
        for(int i=1;i<=q;i++)
        {
            s[i].l=read();
            s[i].r=read();
            s[i].id=i;
        }
        sort(s+1,s+q+1,cmp);
        int L=0,R=n+1;
        sum=0;
        for(int i=1;i<=q;i++)
        {
            while(L<s[i].l){L++;inc(a[L]);}
            while(L>s[i].l){dec(a[L]);L--;}
            while(R<s[i].r){dec(a[R]);R++;}
            while(R>s[i].r){R--;inc(a[R]);}
            ans[s[i].id]=sum;
        }
        for(int i=1;i<=q;i++)
            printf("%d\\n",ans[i]);
    }
}

 

这还是牛客网暑假第一场的签到题…………

 

以上是关于求区间不同数的个数的主要内容,如果未能解决你的问题,请参考以下文章

SPOJ 3267 D-query(离散化+主席树求区间内不同数的个数)

主席树的各类模板(区间第k大数动,静,区间不同数的个数,区间<=k的个数)

BZOJ 3289 Mato的文件管理 | 莫队 树状数组

2017乌鲁木齐区域赛K(容斥原理求指定区间内与n互素的数的个数)

hdu 5919 主席树(区间不同数的个数 + 区间第k大)

11.8