题目链接:
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

题意:
给你 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 ;
}