SPOJ - DQUERY query 【主席树】询问区间中不同的数字个数 and HDU - 5919 【主席树求不同个数 + 思维】

传送门
题目大意: 询问区间不同数的个数有多少个.

做法: 主席树维护, 也就是如果当前这个数没有出现过, 那么我们就新开一颗线段树维护, 区间长度++, 如果是之前出现过, 那么把之前出现的位置的线段树先减掉, 然后再在当前这颗线段树再加回去, 这样就保证了每个区间中相同种数出现的次数最多为一次, 这样直接询问区间中的值即可.

AC Code

const int maxn = 1e5+5;
int n, m;
struct Tree {
    int ls, rs, val; // 左右儿子的编号, 和维护的一个值.
}tre[maxn*40];
int idx = 0, root[maxn];
int build(int l, int r) {
    int nod = ++idx;
    tre[nod].val = 0;
    if (l == r) return nod;
    int mid = (l + r) >> 1;
    tre[nod].ls = build(l, mid);
    tre[nod].rs = build(mid+1, r);
    return nod;
}
int update(int pre, int l, int r, int pos, int v) {
    int nod = ++idx;
    tre[nod] = tre[pre]; tre[nod].val += v;
    if (l == r) return nod;
    int mid = (l + r) >> 1;
    if (pos <= mid) tre[nod].ls = update(tre[pre].ls, l, mid, pos, v);
    else tre[nod].rs = update(tre[pre].rs, mid+1, r, pos, v);
    return nod;
}
int query(int id, int l, int r, int pos) {
    if (l == r) return tre[id].val;
    int mid = (l + r) >> 1;
    if (pos <= mid) {
        return tre[tre[id].rs].val + query(tre[id].ls, l, mid, pos);
    }
    else return query(tre[id].rs, mid+1, r, pos);
}
int a[maxn], vis[maxn*10];
void solve() {
    scanf("%d", &n);
    for (int i = 1 ; i <= n ; i ++) {
        scanf("%d", a+i);
    }
    root[0] = build(1, n);    //根节点
    for (int i = 1 ; i <= n ; i ++) {
        if (!vis[a[i]])
            root[i] = update(root[i-1], 1, n, i, 1);
        else {
            int t = update(root[i-1], 1, n, vis[a[i]], -1);
            root[i] = update(t, 1, n, i, 1);
        }
        vis[a[i]] = i;
    }
    scanf("%d", &m);
    while (m--) {
        int l, r; scanf("%d%d", &l, &r);
        int tmp = query(root[r], 1, n, l);
        printf("%d\n", tmp);
    }
}

HDU - 5919
题目大意: 每次询问一段区间, 区间中不同数个数第一次出现的位置为一个序列, 问第k/2(向上取整)个数是多少,

思路: 我们再单纯求区间中不同个数的时候, 做法是减去之前出现的位置上面标记, 而这样我们其实是知道是维护的最后一次出现该种数字的位置, 这道题要的是第一次出现这个位置的, 所以我们倒着做呀, 这样一个区间中有这个数字的位置一定就是这个数字第一次出现的位置, 然后找到区间中有多少个不同的数字, 再在主席树中二分找到那个位置即可… 复杂度(nlogn)

AC Code

const int maxn = 2e5+5;
int n, m;
struct Tree {
    int ls, rs, val; // 左右儿子的编号, 和维护的一个值.
}tre[maxn*40];
int idx, root[maxn];
int build(int l, int r) {
    int nod = ++idx;
    tre[nod].val = 0;
    if (l == r) return nod;
    int mid = (l + r) >> 1;
    tre[nod].ls = build(l, mid);
    tre[nod].rs = build(mid+1, r);
    return nod;
}
int update(int pre, int l, int r, int pos, int v) {
    int nod = ++idx;
    tre[nod] = tre[pre]; tre[nod].val += v;
    if (l == r) return nod;
    int mid = (l + r) >> 1;
    if (pos <= mid) tre[nod].ls = update(tre[pre].ls, l, mid, pos, v);
    else tre[nod].rs = update(tre[pre].rs, mid+1, r, pos, v);
    return nod;
}
int query(int id, int l, int r, int pos) {
    if (l == r) return tre[id].val;
    int mid = (l + r) >> 1;
    if (pos > mid) {
        return tre[tre[id].ls].val + query(tre[id].rs, mid+1, r, pos);
    }
    else return query(tre[id].ls, l, mid, pos);
}
int Find(int id, int l, int r, int k) {
    if (l == r) return l;
    int mid = (l + r) >> 1;
    int num = tre[tre[id].ls].val;
    if (num >= k) {
        return Find(tre[id].ls, l, mid, k);
    }
    else return Find(tre[id].rs, mid+1, r, k-num);
}
int a[maxn], vis[maxn];
void solve() {
    scanf("%d%d", &n, &m); idx = 0;
    for (int i = 1 ; i <= n ; i ++) {
        scanf("%d", a+i);
    } Fill(vis, 0);
    root[n+1] = build(1, n);    //根节点
    for (int i = n ; i >= 1 ; i --) {
        if (!vis[a[i]])
            root[i] = update(root[i+1], 1, n, i, 1);
        else {
            int t = update(root[i+1], 1, n, vis[a[i]], -1);
            root[i] = update(t, 1, n, i, 1);
        }
        vis[a[i]] = i;
    }
    int ans = 0;
    while (m--) {
        int l, r; scanf("%d%d", &l, &r);
        int tl = min((l+ans) % n + 1, (r+ans) % n + 1);
        int tr = max((l+ans) % n + 1, (r+ans) % n + 1);
        if (tl > tr) swap(tl, tr);
        int tmp = query(root[tl], 1, n, tr); tmp = (tmp+1)/2;
        ans = Find(root[tl], 1, n, tmp);
        printf(" %d", ans);
    }
}

求区间中不同数个数的精华为一段区间中不同数的个数最多只出现了一次……!!!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值