spoj D-query 区间不同数个数 主席树||离线+树状数组

把区间统计转化为前缀和,这里的前缀和不是普通的前缀和,对相同的ai,只有最右边那个才是有效的。

举个栗子:1 2 2 1 3 这样一个序列有效是这个样子 * * 2 1 3 ,因为1在后面出现过所以前一个无效,同理 2。前缀和则是0 0 1 2 3 ,那么对区间[1,5],[2,5],[3,5],[4,5],..[x,5] 我们都可以用sum[5]-sum[x-1]来求。

这里的一个问题是前缀和是对右端点确定的情况,所以只用一棵线段树或树状数组来维护的话就要离线,对查询的右端点进行排序。

如果强制在线怎么做呢,那就是主席树,简单理解为对每一个前缀建一棵线段树(这就是主席树做的事情),然后查询就是到右端点对应那棵树里去查询就ok了。



离线+树状数组

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

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

int main()
{
    int n;
    while(~scanf("%d",&n)){
        fill(data,data+n+2,0);
        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){
                    add(mp[a[j]],-1);
                }
                add(j,1);
                mp[a[j]]=j;
            }
            pre=q[i].r+1;
            ans[q[i].id]=sum(q[i].r)-sum(q[i].l-1);
        }
        for(int i=0;i<m;i++){
            printf("%d\n",ans[i]);
        }
    }
    return 0;
}



主席树

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <map>
#define mid (l+r>>1)

using namespace std;
const int maxn=30000+5;
int a[maxn],root[maxn];
int data[maxn*20],ls[maxn*20],rs[maxn*20];
int cnt;
map<int,int> mp;
int build(int l,int r){
    int t=cnt++;
    data[t]=0;
    if(l==r)return t;
    ls[t]=build(l,mid);
    rs[t]=build(mid+1,r);
    return t;
}
int update(int p,int l,int r,int v,int w){
    int t=cnt++;
    data[t]=data[p]+w;
    if(l==r) return t;
    if(v<=mid){
        rs[t]=rs[p];
        ls[t]=update(ls[p],l,mid,v,w);
    }
    else{
        ls[t]=ls[p];
        rs[t]=update(rs[p],mid+1,r,v,w);
    }
    return t;
}
int query(int t,int l,int r,int v){
    if(l==r)return data[t];
    if(v<=mid)return query(ls[t],l,mid,v)+data[rs[t]];
    else return query(rs[t],mid+1,r,v);
}

int main()
{
    int n;
    while(~scanf("%d",&n)){
        cnt=1;mp.clear();
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
        }
        root[0]=build(1,n);
        for(int i=1;i<=n;i++){
            if(mp[a[i]]==0)
                root[i]=update(root[i-1],1,n,i,1);
            else {
                int tmp=update(root[i-1],1,n,mp[a[i]],-1);
                root[i]=update(tmp,1,n,i,1);
            }
            mp[a[i]]=i;
        }
        int m;
        scanf("%d",&m);
        for(int i=0;i<m;i++){
            int l,r;
            scanf("%d%d",&l,&r);
            printf("%d\n",query(root[r],1,n,l));
        }

    }
    return 0;
}





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值