题意:
给你一个长度为n的序列,有q次询问
每次询问l,r问你,a1,a2,a3,......al,ar,ar+1,.....an中有多少个不同的数
解析:
一开始我是用莫队做的,正的反的都做了一遍
正的是遍历询问区间的时候l=0,r=n+1,是从两端往里缩的
用vis[i]记录的是当前[1,l]+[r,n]中i的个数,那么转移的时候,只要vis[i]从0变到1就ans++,vis[i]从1变到0就ans--
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef long long int ll;
const int MAXN = 1e5+100;
int n,res,m;
int ans;
int pos[MAXN]; //i位置是属于第pos[i]块的,用于对询问进行排序
int vis[MAXN];
int gg[MAXN];
int a[MAXN];
struct node{
int l;
int r;
int id;
int ans;
}q[MAXN];
bool cmp(node a,node b)
{
if(pos[a.l]==pos[b.l]) return a.r>b.r;
return a.l<b.l;
}
bool cmp1(node a,node b)
{
return a.id<b.id;
}
inline void updateplus(int x)
{
vis[x]+=1;
/*if(vis[x]==1)
ans++;*/
ans+=(vis[x]==1?1:0);
}
inline void updateminus(int x)
{
vis[x]-=1;
/*if(vis[x]==0)
ans--;*/
ans-=(vis[x]==0?1:0);
}
inline void solve()
{
int i,l,r;
l=0;r=n+1;
for(i=1;i<=m;i++)
{
while(l<q[i].l) updateplus(a[l+1]),l++; //将l向右移至q[i].l
while(l>q[i].l) updateminus(a[l]),l--; //将l向右移至q[i].l
while(r>q[i].r) updateplus(a[r-1]),r--; //将r左移至q[i].r
while(r<q[i].r) updateminus(a[r]),r++; //将r/右移至q[i].r
gg[q[i].id]=ans;
}
}
int main()
{
int k;
while(scanf("%d%d",&n,&m)!=EOF)
{
int block=sqrt(n);
ans=0;
for(int i=0;i<=n;i++) vis[i]=0;
res=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
pos[i]=(i-1)/block;
}
a[n+1]=0;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
}
sort(q+1,q+m+1,cmp); //按照块来排序
solve();
//sort(q+1,q+m+1,cmp1);
for(int i=1;i<=m;i++)
{
printf("%d\n",gg[i]);
}
}
return 0;
}
反的话我是vis[i]记录除[l,r]外i的个数,那么我们就可以从l=1,r=0开始,按普通的莫队来扫,都逐渐往右扫。
ans表示的是[l,r]内不同数的个数。一开始vis需要预处理出来,因为一开始区间是空的。当vis[i]从1变成0,ans++
vis[i]从0变成1,ans--。这样每次询问答案就是总的不同数的个数-ans
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef long long int ll;
const int MAXN = 1e5+100;
int n,res,m;
int ans;
int pos[MAXN]; //i位置是属于第pos[i]块的,用于对询问进行排序
int vis[MAXN];
int a[MAXN];
struct node{
int l;
int r;
int id;
int ans;
}q[MAXN];
bool cmp(node a,node b)
{
if(pos[a.l]==pos[b.l]) return a.r<b.r;
return a.l<b.l;
}
bool cmp1(node a,node b)
{
return a.id<b.id;
}
void update1(int x)
{
vis[x]+=1;
/*if(add>0&&vis[x]==1)
ans--;*/
ans-=(vis[x]==1?1:0);
}
void update2(int x)
{
vis[x]-=1;
/*if(add<0&&vis[x]==0)
ans++;
else if(add>0&&vis[x]==1)
ans--;*/
ans+=(vis[x]==0?1:0);
}
void solve()
{
int i,l,r;
l=1;r=0;
ans=0;
for(i=1;i<=m;i++)
{
if(q[i].ans!=-1) continue;
while(l<q[i].l) update1(a[l]),l++; //将l向右移至q[i].l
while(l>q[i].l) update2(a[l-1]),l--; //将l向左移至q[i].l
while(r>q[i].r) update1(a[r]),r--; //将r左移至q[i].r
while(r<q[i].r) update2(a[r+1]),r++; //将r右移至q[i].r
q[i].ans=res-ans;
}
}
int main()
{
int k;
while(scanf("%d%d",&n,&m)!=EOF)
{
int block=sqrt(n);
//for(int i=0;i<=n;i++) vis[i]=0;
memset(vis,0,sizeof(vis));
res=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
pos[i]=(i-1)/block;
if(vis[a[i]]==0) res++;
vis[a[i]]++;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&q[i].l,&q[i].r);
q[i].ans=-1;
//if(q[i].l>q[i].r) swap(q[i].l,q[i].r);
if(q[i].l==q[i].r||q[i].l+1==q[i].r||q[i].l>q[i].r) q[i].ans=res;
q[i].l++;
q[i].r--;
q[i].id=i;
}
sort(q+1,q+m+1,cmp); //按照块来排序
solve();
sort(q+1,q+m+1,cmp1);
for(int i=1;i<=m;i++)
{
printf("%d\n",q[i].ans);
}
}
return 0;
}
主席树rt[i]维护的是a[1..i]的不同的数的最后一次出现的位置
我们先将a,扩展成a[1...2n](a[1..n]=a[n+1...2n])
然后按照下标插入主席树,插入a[i]时,若a[i]是第一次出现,那么就将主席树对应i位置值+1
否则,就要在rt[i]这棵树内将a[i]原来的位置-1,再把i的位置+1(维护最后出现的位置)
那么对于查询[l,r],就是查询[r,l+n],我们只需要在rt[l+n]这棵树中,查询[r,l+n]这个区间的不同数的个数。其实只需查询r位置的叶子节点,然后回溯的时候把右子树的值都加上就可以了。
这样做的原因是在[r,l+n]区间内的不同的数,在[①,l+n]最后出现的位置一定是在[r,l+n]中的
所以在[①...l+n]维护最后出现的位置一定可以查询出[k,l+n](k<=l+n)内不同数的个数
/*********分割线***********/
这里的主席树不具有前缀的性质,这里这样做主要是为了在线查询。
其实如果将询问排序,按l+n(右边界值)从小到大排序,那么其实只需要一棵线段树就够了
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 1e5+5;
//const int MAXM = (1e5+5)*15;
typedef struct node
{
int x;
int id;
}node;
int a[MAXN];
int ran[MAXN];
int rt[MAXN*20],ls[MAXN*80],rs[MAXN*80],tot,sz;
int sum[MAXN*80];
int vis[MAXN];
bool cmp(node a,node b)
{
return a.x<b.x;
}
void build(int& root,int l,int r)
{
root=++tot;
sum[root]=0;
if(l==r) return;
int mid=(l+r)>>1;
build(ls[root],l,mid);
build(rs[root],mid+1,r);
}
void update(int& root,int l,int r,int last,int x,int val)
{
root=++tot;
ls[root]=ls[last];
rs[root]=rs[last];
sum[root]=sum[last]+val;
if(l==r) return ;
int mid=(l+r)>>1;
if(mid>=x) update(ls[root],l,mid,ls[last],x,val);
else update(rs[root],mid+1,r,rs[last],x,val);
}
int query(int tt,int l,int r,int x)
{
if(l==r)
{
return sum[tt];
}
int mid=(l+r)>>1;
if(mid>=x)
return query(ls[tt],l,mid,x)+sum[rs[tt]];
else
return query(rs[tt],mid+1,r,x);
}
int main()
{
int t;
int n,m;
while(~scanf("%d%d",&n,&m))
{
for(int i=1;i<=n;i++) scanf("%d",&a[i]),vis[i]=0;
tot=0;
//sort(ran+1,ran+1+n);
//sz=unique(ran+1,ran+1+n)-(ran+1);
//for(int i=1;i<=n;i++) a[i]=lower_bound(ran+1,ran+1+sz,a[i])-ran;
sz=n;
build(rt[0],1,sz);
for(int i=1;i<=n;i++)
{
int x=a[i];
if(!vis[x])
update(rt[i],1,sz,rt[i-1],i,1);
else
{
int t;
update(t,1,sz,rt[i-1],vis[x],-1);
update(rt[i],1,sz,t,i,1);
}
vis[x]=i;
}
for(int i=n+1;i<=2*n;i++)
{
int x=a[i-n];
if(!vis[x])
update(rt[i],1,sz,rt[i-1],i,1);
else
{
int t;
update(t,1,sz,rt[i-1],vis[x],-1);
update(rt[i],1,sz,t,i,1);
}
vis[x]=i;
}
//for(int i=n+1;i<=2*n;i++) update(rt[i],1,sz,rt[i-1],a[i-n]);
for(int i=0;i<m;i++)
{
int l,r;
scanf("%d%d",&l,&r);
int ind=query(rt[l+n],1,sz,r);
printf("%d\n",ind);
}
}
return 0;
}