[ SDOI 2009 ] HH的项链 & [ HEOI 2012 ] 采花

\(\\\)

\(Description\)


给出一个长为\(N\)的序列,\(M\)次询问区间\([L_i,R_i]\)内不同数字的个数。

  • \(N\in [1,5\times 10^4]\)\(M\in [1,2\times 10^5]\)\(L_i,R_i\in [1,N]\)

\(\\\)

\(Solution\)


  • 离线做法,将询问按右端点从小到大排序。
  • 对下标开树状数组,每次添加数字在该位置\(+1\),在上一次该数字出现的位置\(-1\),然后将能回答询问以前缀和相减的方式都回答了。
  • 关于正确性,如上做法可以看成只为每个颜色保留最后一次出现的标记,因为将询问排序过,所以更新的树状数组所求出的答案一定是对于当前右端点是合法的。

\(\\\)

\(Code\)


#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define R register
#define N 500010
#define M 200010
using namespace std;
 
int n,m,clr[N],ans[M],last[1000010];
 
struct tasks{int l,r,num,ans;}tsk[M];
 
struct BIT{
    int c[N];
    BIT(){memset(c,0,sizeof(c));}
    inline int lowbit(int x){return x&(-x);}
    inline void add(int x,int k){
        for(R int i=x;i<=n;i+=lowbit(i)) c[i]+=k;
    } 
    inline int sum(int x){
        int res=0;
        for(R int i=x;i;i-=lowbit(i)) res+=c[i];
        return res;
    }
}bit;
 
inline int rd(){
    int x=0;
    char c=getchar();
    while(!isdigit(c)) c=getchar();
    while(isdigit(c)){
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return x;
}
 
inline bool cmp(tasks a,tasks b){
    return (a.r==b.r)?(a.l<b.l):(a.r<b.r);
}
 
int main(){
    n=rd();
    for(R int i=1;i<=n;++i) clr[i]=rd();
    m=rd();
    for(R int i=1;i<=m;++i) {
        tsk[i].l=rd();
        tsk[i].r=rd();
        tsk[i].num=i;
    }
    sort(tsk+1,tsk+1+m,cmp);
    int now=1;
    for(R int i=1;i<=n;++i){
        bit.add(i,1);
        if(last[clr[i]]) bit.add(last[clr[i]],-1);
        last[clr[i]]=i;
        while(i==tsk[now].r){
            tsk[now].ans=bit.sum(i)-bit.sum(tsk[now].l-1);
            ++now;
        }
        if(now>m)break;
    }
    for(R int i=1;i<=m;++i) ans[tsk[i].num]=tsk[i].ans;
    for(R int i=1;i<=m;++i) printf("%d\n",ans[i]);
    return 0;
}

\(\\\)

\(Extend\)


将询问改为,求区间内至少出现过两次的不同数字个数。

\(\\\)

  • 注意到只有询问区间内出现两次数字才是有效的,但仿照之前的写法如果直接标记第二次出现的数,并取消第一次出现的数却会出错,是因为可能所谓的第一次出现的数并不在询问区间里,而所谓的第二个数却被算进了答案。
  • 于是做法就改为记录两次上一个出现的位置,每次出现一个数将两次前出现该数的位置\(-1\),一次前出现的位置\(+1\)即可,这样能保证一个数只算了一次,并且出现两次的数一定打过标记。

\(\\\)

\(Code\)


#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 2000010
#define R register
#define gc getchar
using namespace std;
 
inline int rd(){
  int x=0; bool f=0; char c=gc();
  while(!isdigit(c)){if(c=='-')f=1;c=gc();}
  while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
  return f?-x:x;
}
 
int n,c,m,s[N],lst1[N],lst2[N],ans[N];
 
struct query{int l,r,num;}q[N];
 
inline bool cmp(query x,query y){
  return x.r==y.r?x.l<y.l:x.r<y.r;
}
 
struct BIT{
  int c[N];
  BIT(){memset(c,0,sizeof(c));}
  inline int lowbit(int x){return x&-x;}
  inline void add(int p,int x){
    for(;p<=n;p+=lowbit(p)) c[p]+=x;
  }
  inline int sum(int p){
    int res=0;
    for(;p;p-=lowbit(p)) res+=c[p];
    return res;
  }
}bit;
 
int main(){
  n=rd(); c=rd(); m=rd();
  for(R int i=1;i<=n;++i) s[i]=rd();
  for(R int i=1;i<=m;++i){
    q[i].l=rd(); q[i].r=rd(); q[i].num=i;
  }
  sort(q+1,q+1+m,cmp);
  for(R int i=1,top=1;i<=n;++i){
    if(lst2[s[i]]) bit.add(lst2[s[i]],-1);
    if(lst1[s[i]]){bit.add(lst1[s[i]],1);}
    lst2[s[i]]=lst1[s[i]]; lst1[s[i]]=i;
    while(q[top].r==i){
      ans[q[top].num]=bit.sum(i)-bit.sum(q[top].l-1);
      ++top;
    }
  }
  for(R int i=1;i<=m;++i) printf("%d\n",ans[i]);
  return 0;
}
posted @ 2018-09-08 18:14  SGCollin  阅读(135)  评论(0编辑  收藏  举报