D-query SPOJ - DQUERY (主席树入门)


题目链接


题意:给定序列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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值