D-query SPOJ - DQUERY (树状数组离线预处理||主席树)

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 ≤ 10^6).
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个数,有m次询问,每次查询一个区间[l,r],问这个区间里有多少不同的数。

思路:离线预处理+树状数组或者用主席树在线操作。

代码如下

/*
树状数组
*/
#include<bits/stdc++.h>
using namespace std;

int n;
int a[30005];
int nex[30005];//记录与第i位置上相同的数的下一个位置坐标
int last[1000005];//记录值a[i]最后出现的位置,a[i]的范围可达1e6,所以该数组需要开到1e6
int Tree[30005];
int ans[200005];
struct node
{
    int l,r;
    int id;
}q[200005];
bool cmp(node x,node y)
{
    return x.l<y.l;
}
int lowbit(int x)
{
    return x&-x;
}
int sum(int x)
{
    int ans=0;
    while(x>0)
    {
        ans+=Tree[x];
        x-=lowbit(x);
    }
    return ans;
}
void add(int x,int date)
{
    while(x<=n)
    {
        Tree[x]+=date;
        x+=lowbit(x);
    }
}
int main() 
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]),nex[i]=n+1;
    memset(last,-1,sizeof(last));
    for(int i=1;i<=n;i++)
    {
        if(last[a[i]]==-1)//如果该位置上的数字是第一次出现,那么add(i,1)
        {
            last[a[i]]=i;
            add(i,1);
        }
        else 
        {
            nex[last[a[i]]]=i;
            last[a[i]]=i;
        }
    }

    int m;
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
    sort(q+1,q+1+m,cmp);
    int cnt=1;
    for(int i=1;i<=m;i++)
    {
        while(q[i].l>cnt)
        {
            add(cnt,-1);//此时我们需要将cnt位置上的值舍去,所以该位置-1
            if(nex[cnt]<=n)//如果下一个会出现相同值的数,那么再在下一个位置上加1
            add(nex[cnt],1);
            cnt++;
        }
        ans[q[i].id]=sum(q[i].r)-sum(q[i].l-1);//答案即为sum(r)-sum(l-1)
    }
    for(int i=1;i<=m;i++)
    printf("%d\n",ans[i]);
    return 0;
}

主席树

#include<bits/stdc++.h>
using namespace std;
const int maxn=30005;
int a[maxn],rt[maxn];//保存多颗线段树的根节点

int tot=0;
struct node
{
    int l,r;//分别指向左右子树
    int size;//该节点对应区间数的个数
}CT[maxn*30];//chairtree

int build(int l,int r)
{
    int root=++tot;
    CT[root].size=0;
    if(l==r) return root;
    int m=(l+r)>>1;
    CT[root].l=build(l,m);
    CT[root].r=build(m+1,r);
    return root;
}
int Update(int l,int r,int root,int x,int val)
{
    int tmp=++tot;
    CT[tot].size=CT[root].size+val;
    int m=(l+r)>>1;
    if(l==r) return tmp;
    else if(x<=m) 
    {
        CT[tmp].l=Update(l,m,CT[root].l,x,val);
        CT[tmp].r=CT[root].r;
    }
    else 
    {
        CT[tmp].l=CT[root].l;
        CT[tmp].r=Update(m+1,r,CT[root].r,x,val);
    }
    return tmp;
}
int query(int l,int r,int root,int pos)
{
    if(l==r) return CT[root].size;
    int m=(l+r)>>1;
    if(pos<=m) 
        return CT[CT[root].r].size+query(l,m,CT[root].l,pos);    
    else 
        return query(m+1,r,CT[root].r,pos);
}
map<int,int> mp;
int main() 
{
    int n,m;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);

    rt[0]=build(1,n);

    for(int i=1;i<=n;i++)
    {
        if(mp.find(a[i])==mp.end())
        {
            mp[a[i]]=i;
            rt[i]=Update(1,n,rt[i-1],i,1);      
        }
        else 
        {
            int tmp=Update(1,n,rt[i-1],mp[a[i]],-1);
            rt[i]=Update(1,n,tmp,i,1);  
        }   
        mp[a[i]]=i; 
    }
    scanf("%d",&m);
    while(m--)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%d\n",query(1,n,rt[y],x));  
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值