[SDOI2009]HH的项链

题目描述

HH 有一串由各种漂亮的贝壳组成的项链。HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。HH 不断地收集新的贝壳,因此,他的项链变得越来越长。有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?这个问题很难回答……因为项链实在是太长了。于是,他只好求助睿智的你,来解决这个问题。

输入格式

第一行:一个整数N,表示项链的长度。

第二行:N 个整数,表示依次表示项链中贝壳的编号(编号为0 到1000000 之间的整数)。

第三行:一个整数M,表示HH 询问的个数。

接下来M 行:每行两个整数,L 和R(1 ≤ L ≤ R ≤ N),表示询问的区间。

输出格式

M 行,每行一个整数,依次表示询问对应的答案。


做法1:线段树/树状数组:

可以知道的是,我们对于每一种颜色都只需要其最近的一个。因为如果最近的一个都不在当前区间,那么之前的就都不会在当前区间了。

那么设c(x)表示1~x包含多少种颜色。那么由于我们只需要知道当前颜色col最近的一个位置,当一个新的col色的贝壳时,我们要能够删掉之前的col的位置,并且加入当前位置。所以我们需要一个支持单点修改、区间查询的数据结构。可以用线段树或者树状数组。

设vis(i)表示颜色i最近一次出现的位置。那么当新的颜色i的贝壳出现时:

Delete(vis[x]),vis[x]=now,Add(now);

所以我们可以先存下所有询问,然后按照右端点下标从小到大排序,然后枚举一个指针扫描过去,不断进行刚才更新颜色的操作,然后回答询问即可。

时间复杂度为O(MlogM+(N+M)logN)。附上树状数组的代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define maxn 500001
#define maxval 1000001
using namespace std;
int n,m,col[maxn],ans[maxn];
inline int read(){
    register int x(0),f(1); register char c(getchar());
    while(c<'0'||'9'<c){ if(c=='-') f=-1; c=getchar(); }
    while('0'<=c&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
    return x*f;
}

int c[maxn];
inline int lowbit(const int &x){ return x&(-x); }
inline void add(int x,const int &y){ for(;x<=n;x+=lowbit(x)) c[x]+=y; }
inline int sum(int x){ register int ans=0; for(;x;x-=lowbit(x)) ans+=c[x]; return ans; }
inline int ask(int l,int r){ return sum(r)-sum(l-1); }

struct query{
    int l,r,id;
    bool operator<(const query &q)const{ return r<q.r; }
}q[maxn];
int vis[maxval];
int main(){
    n=read();
    for(register int i=1;i<=n;i++) col[i]=read();
    m=read();
    for(register int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].id=i;

    sort(q+1,q+1+m);
    int r=0;
    for(register int i=1;i<=m;i++){
        while(r+1<=q[i].r){
        	r++;
            if(vis[col[r]]) add(vis[col[r]],-1);
            add(r,1),vis[col[r]]=r;
        }
        ans[q[i].id]=ask(q[i].l,q[i].r);
    }
    for(register int i=1;i<=m;i++) printf("%d\n",ans[i]);
    return 0;
}

做法2:莫队。

莫队的板子题,设cnt(x)表示当前区间内颜色x出现的次数,每次往区间内加入一个点时就将这个点的颜色对应的cnt+1。如果cnt此时等于1,那么答案加1。从区间删点类似。

时间复杂度为O(N√N+MlogM)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define maxn 500001
#define maxval 1000001
#pragma GCC optimize(2)
using namespace std;
int n,m,unit,val[maxn];
inline int read(){
    register int x(0),f(1); register char c(getchar());
    while(c<'0'||'9'<c){ if(c=='-') f=-1; c=getchar(); }
    while('0'<=c&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
    return x*f;
}
void write(int x){
    if(!x) return;
    write(x/10),putchar(x%10+'0');
}

struct query{
    int l,r,id,col,ans;
}q[maxn];
int cnt[maxval],tot;
bool cmp1(const query &a,const query &b){ return (a.col^b.col)?a.col<b.col:((a.col&1)?a.r<b.r:a.r>b.r); }
inline bool cmp2(const query &x,const query &y){ return x.id<y.id; }
inline void add(const int &x){ tot+=(++cnt[val[x]]==1); }
inline void del(const int &x){ tot-=(--cnt[val[x]]==0); }
inline void solve(){
    sort(q+1,q+1+m,cmp1);
    register int l=0,r=0;
    for(register int i=1;i<=m;i++){
        while(l<q[i].l) del(l++);
        while(l>q[i].l) add(--l);
        while(r<q[i].r) add(++r);
        while(r>q[i].r) del(r--);
        q[i].ans=tot;
    }
    sort(q+1,q+1+m,cmp2);
}

int main(){
    n=read(),unit=sqrt(n);
    for(register int i=1;i<=n;i++) val[i]=read();
    m=read();
    for(register int i=1;i<=m;i++){
        int l=read(),r=read();
        q[i].l=l,q[i].r=r,q[i].id=i,q[i].col=l/unit+1;
    }
    solve();
    for(register int i=1;i<=m;i++) write(q[i].ans),puts("");
    return 0;
}
posted @ 2019-06-21 19:58  修电缆的建筑工  阅读(133)  评论(0编辑  收藏  举报