SPOJ D-query(主席树入门)

  • 题意: 求l,r区间中不同数字的个数
  • 思路: 主席树,第i颗树代表了插入a[i]后,1~i区间内的信息
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3e4+10;
struct node{
    int l,r,sum;
}sgt[N*40];
int a[N],root[N],now;
map<int,int> mp;    // 记录上一次 k 出现的位置
void insert(int l,int r,int pre,int rt,int pos,int val){
    sgt[rt].sum = sgt[pre].sum; // 先将前一颗树的信息赋给当前树
    sgt[rt].l = sgt[pre].l; sgt[rt].r = sgt[pre].r; 
    if(l==r){   // 叶子节点直接更新     叶子节点sum 取值{0,1}
        sgt[rt].sum += val; return ;
    }
    int mid = (l+r)>>1;
    if(pos <= mid) insert(l,mid,sgt[pre].l,sgt[rt].l=++now,pos,val);    // 去左子树插入
    else insert(mid+1,r,sgt[pre].r,sgt[rt].r=++now,pos,val);            // 去右子树插入
    sgt[rt].sum = sgt[sgt[rt].l].sum + sgt[sgt[rt].r].sum;              // 更新父亲
}
int query(int rt,int l,int r,int L,int R){
    if(L >= l && R <= r)    return sgt[rt].sum;                         // 区间包含
    int mid = (L+R)>>1;
    int res = 0;
    if(l<=mid)  res += query(sgt[rt].l,l,r,L,mid);                      // 询问左子树
    if(r>mid)   res += query(sgt[rt].r,l,r,mid+1,R);                    // 询问右子树
    return res;
}
int main(){
    int l,r,k,n,q;
    scanf("%d",&n);
    for(int i=1;i<=n;++i)   scanf("%d",&a[i]);
    for(int i=1;i<=n;++i){
        // 如果没有出现过 直接在i处增加1个
        if(!mp[a[i]]) insert(1,n,root[i-1],root[i]=++now,i,1);          
        else{ // 否则先在i插入 再在mp[a[i]]处反向插入,因为前面的个数不应该增加
            insert(1,n,root[i-1],root[i]=++now,i,1);                    
            int cid = ++now;    // 记录下新的树根
            insert(1,n,root[i],cid,mp[a[i]],-1);
            root[i] = cid;      // 原来的树根不要了 要更新好的树根
        }
        mp[a[i]] = i;   // 更新a[i]的位置
    }
    scanf("%d",&q);
    for(int i=1;i<=q;++i){
        scanf("%d%d",&l,&r);
        printf("%d\n",query(root[r],l,r,1,n));  // 使用r的树根来查询
    }
    return 0;
}

有点想不通的是为什么最后的询问要用右端点的树来查询,而不能直接用最后一颗树查询(按理说后面的树不会更改前面树的信息吧...)

上一篇:2014 39th ACM-ICPC 北京赛区 总结


下一篇:MySQL外键字段为什么必须加索引