题目链接
题意:给定序列a,给出q次区间询问,每次询问[l,r]区间内有多少个不同的数。
分析:
我们先分析右端点r固定不变的情况,如何求[l,r]有多少个不同的数,
比如对于固定的区间[1,n],如何利用线段树统计[l,n]不同的数?
我们用线段树记录区间内不同数的个数(这里不是用权值线段树,而是普通地对定义域建树)。
开始是一棵空树,
我们扫描数组并单点修改,如果当前的数a[i]在之前没有出现过,那么直接使线段树的pos==i处的值为1, 如果之前出现过,我们就把之前出现位置的值变成0,当下出现位置的值变成1 。 (线段树就维护这一串01的区间和)
这样的目的就是:使得统计的时候每个数在最右边出现的地方被统计,便于查询。
我们查询[l,n]不同数的个数的时候,直接找位置不小于l处的区间和就好了。
但是这题的右端点是不固定的,所以我们需要有n棵线段树,即对a数组每个前缀都建一棵线段树来维护不同的右端点。
这样主席树就派上用场了。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 3e4+7;
const int mx = 14;
const int mod = 1e9+7;
const ll inf = 34359738370;
const int INF = 1e6+5;
//给定序列a q次询问 每次询问[l,r]有多少个不同的数
/*建对每个前缀[1,i]建主席树,存[1,i]中不同的数的个数。在添数修改的时候,
如果这个数出现过就把这个数删去,再在删去后的版本上添数修改得到版本root[i],保证每个数在最后出现的位置被计数
从而使得查询的时候,[l,r]我们只要对r版本的线段树查询大于等于l的位置的和就好
*/
int tree[maxn<<5],lc[maxn<<5],rc[maxn<<5],root[maxn],cnt=0;
map<int,int> mp;//记录上一次出现的位置
int a[maxn];
int n,m;
void updata(int &rt,int last,int l,int r,int pos,int v)
{
rt=++cnt;
tree[rt]=tree[last]+v;
lc[rt]=lc[last],rc[rt]=rc[last];
if(l == r) return ;
int mid=(l+r)>>1;
if(pos<=mid) updata(lc[rt],lc[last],l,mid,pos,v);
else updata(rc[rt],rc[last],mid+1,r,pos,v);
}
int query(int rt,int l,int r,int pos)//位置不小于pos的区间的和
{
if(l >= pos) return tree[rt];
int mid=(l+r)>>1;
if(pos<=mid) return query(lc[rt],l,mid,pos)+tree[rc[rt]];
else return query(rc[rt],mid+1,r,pos);
}
void init()
{
mp.clear();
memset(tree,0,sizeof(tree));
cnt=0;
memset(lc,0,sizeof(lc));
memset(rc,0,sizeof(rc));
}
int main()
{
while(~scanf("%d",&n))
{
init();
for(int i=1;i<=n;i++)
{
scanf("%d",a+i);
int x=mp[a[i]];
if(!x)
{
updata(root[i],root[i-1],1,n,i,1);
}
else
{
int temp;//删完上一次位置后的线段树版本
updata(temp,root[i-1],1,n,x,-1);
updata(root[i],temp,1,n,i,1);//在temp的基础上 使得i处+1
}
mp[a[i]]=i;
}
scanf("%d",&m);
while(m--)
{
int l,r;
scanf("%d %d",&l,&r);
printf("%d\n",query(root[r],1,n,l));
}
}
return 0;
}