主席树入门详解二(学习笔记)(例题SPOJ - DQUERY 求区间数字种类数)

主席树入门详解一链接

https://blog.csdn.net/weixin_42165981/article/details/81131661

Start~

看了前一篇博客,应该已经对最基础的主席树有了一个大概的掌握。主席树的本质就是一堆线段树的集合(也就是包含历史版本的线段树),所以需要用一堆线段树来解决的问题,就可以用主席树来解决。主席树与线段树最大的区别就是主席树的左右儿子的节点编号是不固定的。那么我们在编写代码的时候,传入根节点的坐标,然后再记录左右儿子的坐标,这样我们的查询,更新函数,都和普通的线段树差不了多少,关键就是节点的公用关系,和线段树在题目中的意义和用法!(主席树在代码上和线段树差不了多少,主要就是节点的不同!)

那么就开始这次的题目:求区间数字的种类数

题目链接:

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,这样就可以保证在新的右端点固定的区间里,记录的是数字最后一次出现的位置的信息,这样题目就解决了。

也就是说对于这个题目,我们也可以不用主席树,只要对询问排序,然后利用树状数组或者线段树就可以解决这个问题。(离线解法

如果不对询问排序的话,我们就必须要用主席树来解决这个问题了,对每个右端点建立一个线段树。不断查询即可。(在线解法

代码:

1.在线~主席树(理解每个线段树的写法(查询,更新)代码一看就明白了)

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <map>

using namespace std;
const int MAXN=30010;
struct node{
    int l,r,sum;
}T[MAXN*40];
int cnt,a[MAXN],root[MAXN];
map<int,int> pos;

void Init()
{
    cnt=0;
    T[cnt].l=0;T[cnt].r=0;T[cnt].sum=0;
    root[cnt]=0;
    pos.clear();
}

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);
}

int Query(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(l,mid,T[root].l,left)+T[T[root].r].sum;
    else
        return Query(mid+1,r,T[root].r,left);
}

int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        Init();
        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(1,n,root[r],l));
        }
    }
    return 0;
}

2.离线~树状数组(线段树也行)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <cstring>

using namespace std;
const int maxn=30000+5;
map<int,int> mp;
int data[maxn],a[maxn],ans[200000+5];
struct node{
    int l,r,id;
    bool operator<(node t)const{
        return r<t.r;
    }
}q[200000+5];

int lowbit(int x)
{
    return x&-x;
}

int Get_sum(int i)
{
    int ans=0;
    while(i>0){
        ans+=data[i];
        i-=lowbit(i);
    }
    return ans;
}

void Update(int i,int x)
{
    while(i<maxn){
        data[i]+=x;
        i+=lowbit(i);
    }
}

int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        memset(data,0,sizeof(data));
        mp.clear();
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        int m;
        scanf("%d",&m);
        for(int i=0;i<m;i++)
        {
            scanf("%d%d",&q[i].l,&q[i].r);
            q[i].id=i;
        }
        sort(q,q+m);
        int pre=1;
        for(int i=0;i<m;i++)
        {
            for(int j=pre;j<=q[i].r;j++)
            {
                if(mp[a[j]]!=0)
                    Update(mp[a[j]],-1);
                Update(j,1);
                mp[a[j]]=j;
            }
            pre=q[i].r+1;
            ans[q[i].id]=Get_sum(q[i].r)-Get_sum(q[i].l-1);
        }
        for(int i=0;i<m;i++)
            printf("%d\n",ans[i]);
    }
    return 0;
}

 

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值