SPOJ D-query 树状数组离线&&主席树在线

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
题意就是查询区间不同元素的个数,有两种解法。
解法1:离线+树状数组,先把询问离线,并且按照右端点排序,然后从小区间开始,然后树状数组的含义就是指以当前r为结尾的前缀区间的元素种类数,简单点说,就是我当前扫到l , r区间,把l - r区间还没在树状数组上更新的值,更新一遍,在之前已经存在了的值先删掉再更新一遍,确保我确定的元素都是往r靠的,这样才能保证求取区间正确。举个栗子吧:比如我 1 2 2 1 3,当我r移到3的时候,加入前面的1还没在树状数组里更新过(但其实之前已经有读过1了)那就把之前的1的影响删掉,重新在这个3左边这个下标为4的位置给树状数组 add 1.这样确保之后不管我是查询 4 5 或者 1 5,元素1都只算了一次,但都没遗落(想想如果元素1一直在下标1那里,我查询4 5,就不会有1了)

//SPOJ DQUER 170ms 34816kb
#include <bits/stdc++.h>
using namespace std;
const int maxn = 300010;
const int maxq = 1000010;
map <int, int> mp; //记录第一次出现的位置
struct Q{
    int l, r, id;
    Q(){}
    Q(int l, int r, int id) : l(l), r(r), id(id){}
}q[maxq];
bool cmp(Q a, Q b){
    return a.r < b.r;
}
namespace BIT{
    int sum[maxn], n, m;
    inline void init(){memset(sum, 0, sizeof(sum));}
    inline int lowbit(int x){return x & -x;}
    inline void add(int i, int v){for(;i <= n; i+=lowbit(i)) sum[i] += v;}
    inline int query(int i){int res = 0; for(; i; i-=lowbit(i)) res += sum[i]; return res;}
}
using namespace BIT;
int a[maxn], ans[maxq];
int main(){
    while(scanf("%d", &n) != EOF){
        mp.clear();
        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
        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 + m + 1, cmp);
        int cur = 1;
        for(int i = 1; i <= m; i++){
            int id = q[i].id;
            for(int j = cur; j <= q[i].r; j++){
                if(mp.find(a[j]) != mp.end()){
                    add(mp[a[j]], -1);
                }
                add(j, 1);
                mp[a[j]] = j;
            }
            cur = q[i].r + 1;
            ans[id] = query(q[i].r) - query(q[i].l - 1);
        }
        for(int i = 1; i <= m; i++) printf("%d\n", ans[i]);
    }
    return 0;
}

解法2:有了主席树我们为什么要离线呢?对不对。对于一个数,如果以前没出现过,就插入到主席树中,否则就删除以前那个,再插入主席树。注意,所有的更新和删除都是建立了新的节点来保持其历史状态的。对于T[i]我们存的是从1到i区间的不同的数出现了多少个。然后这棵树是根据T[i - 1]来建立的。

//200ms 34816kb
//SPOJ DQUERY
#include <bits/stdc++.h>
using namespace std;
const int maxn = 30010;
int n, q, tot, a[maxn], nxt[maxn], b[maxn];
int T[maxn*40], lson[maxn*40], rson[maxn*40], val[maxn*40];
int build(int l, int r){
    int rt = tot++; val[rt] = 0;
    int mid = (l + r) / 2;
    if(l != r){
        lson[rt] = build(l, mid);
        rson[rt] = build(mid + 1, r);
    }
    return rt;
}
int update(int root, int pos, int v){
    int newroot = tot++, tmp = newroot;
    int l = 1, r = n;
    val[newroot] = val[root] + v;
    while(l < r){
        int m = (l + r) / 2;
        if(pos <= m){
            lson[newroot] = tot++; rson[newroot] = rson[root];
            newroot = lson[newroot]; root = lson[root]; r = m;
        }
        else{
            rson[newroot] = tot++; lson[newroot] = lson[root];
            newroot = rson[newroot]; root = rson[root]; l = m + 1;
        }
        val[newroot] = val[root] + v;
    }
    return tmp;
}
int query(int root, int pos){
    int ret = 0;
    int l = 1, r = n;
    while(l < pos){
        int m = (l + r) / 2;
        if(pos <= m){
            ret += val[rson[root]];
            root = lson[root];
            r = m;
        }
        else{
            l = m + 1;
            root = rson[root];
        }
    }
    return ret + val[root];
}
int main()
{
    while(scanf("%d", &n) != EOF)
    {
        tot = 0;
        memset(nxt, -1, sizeof(nxt));
        for(int i = 1; i <= n; i++){
            scanf("%d", &a[i]);
            b[i-1] = a[i];
        }
        //lisanhua
        sort(b, b+n);
        int cnt = unique(b, b + n)- b;
        T[0] = build(1, n);
        for(int i = 1; i <= n; i++){
            int id = lower_bound(b, b+cnt, a[i]) - b;
            if(nxt[id] == -1){
                T[i] = update(T[i - 1], i, 1);
            }
            else{
                int t  = update(T[i-1], nxt[id], -1);
                T[i] = update(t, i, 1);
            }
            nxt[id] = i;
        }
        scanf("%d", &q);
        while(q--){
            int l, r;
            scanf("%d%d", &l, &r);
            printf("%d\n", query(T[r], l));
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值