1. 程式人生 > >求區間不同數的個數

求區間不同數的個數

add http 方便 void spa printf 一場 using sort

題目: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]);
    }
}

這還是牛客網暑假第一場的簽到題…………

求區間不同數的個數