题目链接:
SPOJ - DQUERY:https://www.spoj.com/problems/DQUERY/en/
题目:
Given a sequence of n numbers a1, a2, ..., an and a number of d-queries. A d-query is a pair (i, j) (1 ≤ i ≤ j ≤ n). For each d-query (i, j), you have to return the number of distinct elements in the subsequence ai, ai+1, ..., aj.
Input
Line 1: n (1 ≤ n ≤ 30000).
Line 2: n numbers a1, a2, ..., an (1 ≤ ai ≤ 106).
Line 3: q (1 ≤ q ≤ 200000), the number of d-queries.
In the next q lines, each line contains 2 numbers i, j representing a d-query (1 ≤ i ≤ j ≤ n).
Output
For each d-query (i, j), print the number of distinct elements in the subsequence ai, ai+1, ..., aj in a single line.
Example
Input
5
1 1 2 1 3
3
1 5
2 4
3 5
Output
3
2
3
题意:
给你 n 个数,然后有 q 个询问,每个询问会给你[l,r],输出[l,r]之间有多少种数字。分析:
首先我们还是思考对于右端点固定的区间(即R确定的区间),我们如何使用线段树来解决这个问题。
我们可以记录每个数字最后一次出现的位置。比如,5这个数字最后一次出现在位置3上,就把位置3记录的信息++(初始化为0)。比如有一个序列 1 2 2 1 3 那么我们记录信息的数列就是 0 0 1 1 1 (2最后出现的位置是位置3 1最后出现的位置是位置4 3最后出现的位置是位置5)。那么对区间 [1,5] , [2,5] , [3,5] , [4,5] , [5,5]的数字种数,我们都可以用sum[5]-sum[x-1]来求(sum数组记录的是前缀和)(前缀和之差可以用线段树或者树状数组来求)。
那么对着区间右端点会变化的题目,我们应该怎么办呢?先思考一下如果右端点有序的话,我们可以怎么做。对R不同的区间,向线段树或者树状数组中添加元素,知道右端点更新为新的R,在添加的过程中,如果这个元素之前出现过,就把之前记录的位置储存的信息 -1,然后在新的位置储存的信息 +1,这样就可以保证在新的右端点固定的区间里,记录的是数字最后一次出现的位置的信息,这样题目就解决了。
也就是说对于这个题目,我们也可以不用主席树,只要对询问排序,然后利用树状数组或者线段树就可以解决这个问题。
如果不对询问排序的话,我们就必须要用主席树来解决这个问题了,对每个右端点建立一个线段树。不断查询即可。(在线解法)
#include<cstdio>
#include<iostream>
#include<fstream>
#include<algorithm>
#include<functional>
#include<cstring>
#include<string>
#include<cstdlib>
#include<iomanip>
#include<numeric>
#include<cctype>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<list>
#include<set>
#include<map>
using namespace std;
const int maxn=1e5+7;
const int mod=1e9+7;
int t,n,m,cnt,root[maxn],a[maxn];
//cnt和root:主席树的总点数和每一个根
struct node
{
int l,r,sum;
} T[maxn*40];
vector<int> v;
int getid(int x) //离散化
{
return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
/*
v.clear();
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
*/
//清空,默认给root[0]
int build(int l,int r)
{
int cur=++cnt;
T[cur].sum=0;
if(l==r)
return cur;
int mid=l+r>>1;
build(l,mid);
build(mid+1,r);
return cur;
}
///单点修改
void update(int l,int r,int &now,int pre,int pos,int add)
{
T[++cnt]=T[pre];T[cnt].sum+=add;now=cnt;
if(l==r)
return;
int mid=(l+r)>>1;
if(pos<=mid)
update(l,mid,T[cnt].l,T[pre].l,pos,add);
else
update(mid+1,r,T[cnt].r,T[pre].r,pos,add);
}
///查询区间[x,y]第k小,使用v[query(1,n,root[x-1],root[y],mid)-1]
int query(int l,int r,int x,int y,int k)
{
if(l==r)
return l;
int mid=(l+r)/2;
int sum=T[T[y].l].sum-T[T[x].l].sum;
if(sum>=k)
return query(l,mid,T[x].l,T[y].l,k);
else
return query(mid+1,r,T[x].r,T[y].r,k-sum);
}
///查询区间[x,y]<=k的元素个数 query_min(root[x-1],root[y],1,n,k)
int two_find(int x,int y,int k)///二分查找第i小的元素小于等于k的最大元素
//即求k是第几小的元素
{
int l=0,r=y-x+1;
while(l<r)
{
int mid=(l+r+1)>>1;
if(v[query(1,n,root[x-1],root[y],mid)-1]<=k)
l=mid;
else
r=mid-1;
}
return l;
}
///查询区间[x,y]>=k的最小值 query_min(root[x-1],root[y],1,n,k)
int query_min(int lrot,int rrot,int l,int r,int k)
{
if(l==r)
{
if(T[rrot].sum-T[lrot].sum>0)
return l;
else
return 1e9;
}
int mid=(l+r)>>1;
if(k<=mid)
{
int ans=1e9;
if(k<=l)//这里相当于一个剪枝,在小于l的时候,如果左子树有符合条件的就进入左子树,否则再进入右子树。
{
if(T[T[rrot].l].sum-T[T[lrot].l].sum>0)
ans=min(ans,query_min(T[lrot].l,T[rrot].l,l,mid,k));
else if(T[T[rrot].r].sum-T[T[lrot].r].sum>0)
ans=min(ans,query_min(T[lrot].r,T[rrot].r,mid+1,r,k));
return ans;
}
if(T[T[rrot].l].sum-T[T[lrot].l].sum>0) //k在l到mid之间的时候,左右子树都有可能涉及,就左右都看一遍寻找最优解
ans=min(ans,query_min(T[lrot].l,T[rrot].l,l,mid,k));
if(T[T[rrot].r].sum-T[T[lrot].r].sum>0)
ans=min(ans,query_min(T[lrot].r,T[rrot].r,mid+1,r,k));
return ans;
}
else
{
int ans=1e9;//k大于mid的时候,直接进入右子树,左子树不用找了
if(T[T[rrot].r].sum-T[T[lrot].r].sum>0)
ans=min(ans,query_min(T[lrot].r,T[rrot].r,mid+1,r,k));
return ans;
}
}
///查询区间[x,y]的种类个数query_num(1,n,root[y],x)
int query_num(int l,int r,int root,int left)
{
if(l>=left)
return T[root].sum;
int mid=(l+r)>>1;
if(mid>=left)
return query_num(l,mid,T[root].l,left)+T[T[root].r].sum;
else
return query_num(mid+1,r,T[root].r,left);
}
map<int,int>pos;
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
root[0]=build(1,n);
pos.clear();
int temp;
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
if(!pos.count(a[i]))//直接加
{
update(1,n,root[i],root[i-1],i,1);
}
else
{
update(1,n,temp,root[i-1],pos[a[i]],-1);
update(1,n,root[i],temp,i,1);
}
pos[a[i]]=i;
}
int q,l,r;
scanf("%d",&q);
for(int i=0; i<q; ++i)
{
scanf("%d%d",&l,&r);
printf("%d\n",query_num(1,n,root[r],l));
}
}
return 0 ;
}