SPOJ D-query 【可持久化线段树】

传送门

题目描述:多次询问一个区间内不同数的数量。

解题思路:一个区间内存在相同的数,所以我们的线段树就不能维护数,应该去维护下标?为什么去维护下标呢,就算一个数相同,但是它们的下标肯定是不同的,我们的线段树维护下标存在的情况。
如果区间内存在重复的数,意味着不同下标对应相同的数,如果我们记录多个下标答案就会重复,我们要怎么去解决这个问题呢?
我们把下标右移(相同的数,我们记录的下标只记录最右边的那一个)。因为如果这个数出现过,为了维护这个状态的线段树,就应该记录下当前的这个下标,取消以前的标记。
我们查询答案时,只需要查询在 r 时刻状态的线段中在 [ l , r ] 区间内下标存在的个数就是答案。

细节请看代码:
 

///#include<bits/stdc++.h>
///#include<unordered_map>
///#include<unordered_set>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<bitset>
#include<set>
#include<stack>
#include<map>
#include<list>
#include<new>
#include<vector>

#define MT(a, b) memset(a,b,sizeof(a));
#define lowbit(x) (x&(-x));
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double pai = acos(-1.0);
const double E = 2.718281828459;
const ll mod = 20071027;
const ll INF = 0x3f3f3f3f3f3f;
const int maxn = 3e4 + 5;

struct node {
    int ls, rs, cnt;
} p[maxn * 40];
int root[maxn], times;
int pos[1000005];

void insert(int &now, int old, int l, int r, int sub, int x) {
    now = ++times;
    p[now] = p[old], p[now].cnt += x;
    if (l == r) return;
    int mid = (l + r) >> 1;
    if (sub <= mid) insert(p[now].ls, p[old].ls, l, mid, sub, x);
    else insert(p[now].rs, p[old].rs, mid + 1, r, sub, x);
}

int query(int now, int l, int r, int a, int b) {
    if (l == a && r == b) return p[now].cnt;
    int mid = (l + r) >> 1;
    if (b <= mid) return query(p[now].ls, l, mid, a, b);
    else if (a > mid) return query(p[now].rs, mid + 1, r, a, b);
    else return query(p[now].ls, l, mid, a, mid) + query(p[now].rs, mid + 1, r, mid + 1, b);
}

void init() {
    times = 0;
    memset(p, 0, sizeof(p));
    memset(root, 0, sizeof(root));
    memset(pos, 0, sizeof(pos));
}

int main() {
    init();
    int n, op, x, l, r;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &x);
        if (pos[x]) {
            insert(root[i], root[i - 1], 1, n, i, 1);
            insert(root[i], root[i], 1, n, pos[x], -1);
        } else
            insert(root[i], root[i - 1], 1, n, i, 1);
        pos[x] = i;
    }
    scanf("%d", &op);
    while (op--) {
        scanf("%d %d", &l, &r);
        printf("%d\n", query(root[r], 1, n, l, r));
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值