CCPC长春站 HDU-5919 Sequence II 主席树

http://acm.hdu.edu.cn/showproblem.php?pid=5919


题意,给出一个序列a1,a2..an,每次给出一个询问L,R,然后对L,R根据上一次的答案处理得到真正的询问区间L,R(第一次询问时默认上一次的答案是0),问,这个区间里的每一个数第一次出现的下标组成的新序列 b1,b2..bk的中位数b(k/2)是多少。 比如原数列7 7 8 8,查询区间[2,4],那么就是7 8 8, 7第一次出现在2,8第一次出现在3,组合起来的b序列是2,3,中位数就是2。


怎么做呢..假如不问我们区间,只求整段序列对应的答案,如果我们求出b序列的总个数k,然后再求出b[(k+1)/2]就可以了。具体做法就是弄一个权值线段树,比如有b序列2,3,4,再来一个辅助数组c,那么c[0] = 0, c[1] = 0,  c[2] = 1, c[3] = 1, c[4] = 1,该线段树表示的就是c的区间和。这样我们就能知道总个数k,然后线段树log(n)查询出第(k+1)/2大是多少。

解决了求整段,现在求区间,自然就用到了主席树,也就是多版本的线段树,每个版本的线段树都基于上一个版本,然后再做出修改,有前缀和或者后缀和的味道。root[i]表示从n构建到i的线段树的根的编号。现在加入有个7 7 8 8的原数列,我从第四个元素开始构建,关键是构造第三个的时候,发现8已经出现过,那么先把位置4路径上的权值全部减了1,再在位置3路径上的权值全部加一。


构造完毕后,我们查询L,R的和,只需要使用T[root[L]]版本的线段树,因为是倒着构建,所以这个版本的线段树是一颗从L开始第一次出现的位置的权值线段树

同理,找第k大的值,也只需要调用T[root[L]]的版本。

因为数据范围都是2 * 1e5,所以不需要离散化

//
//  main.cpp
//  5919 Sequence II 主席树
//
//  Created by czf on 2016/10/29.
//  Copyright © 2016年 czf. All rights reserved.
//

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int MAXN = 2 * 1e5 + 100;
int a[MAXN], p[MAXN], root[MAXN], cnt;

struct {int l, r, sum;} T[MAXN * 40];

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

void update(int l, int r, int &cur, int pre, int pos, int v) {
    cur = ++cnt; T[cur] = T[pre]; T[cur].sum += v;
    if (l == r) return;
    int m = (l + r) >> 1;
    if (pos <= m)
        update(l, m, T[cur].l, T[pre].l, pos, v);
    else
        update(m+1, r, T[cur].r, T[pre].r, pos, v);
}

int getsum(int L, int R, int l, int r, int rt) {
    if (L <= l && r <= R) {
        return T[rt].sum;
    }
    int ret = 0;
    int m = (l + r) >> 1;
    if (L <= m) ret += getsum(L, R, l, m, T[rt].l);
    if (R > m) ret += getsum(L, R, m+1, r, T[rt].r);
    return ret;
}

int query(int l, int r, int rt, int k) {
    if (l == r) {
        return l;
    }
    int m = (l + r) >> 1;
    if (T[T[rt].l].sum >= k)
        return query(l, m, T[rt].l, k);
    else
        return query(m+1, r, T[rt].r, k - T[T[rt].l].sum);
}

int main() {
    int t, kase = 0; scanf("%d",&t);
    while (t--) {
        init();
        int n, m; scanf("%d%d",&n,&m);
        for (int i = 1; i <= n; i ++) scanf("%d",&a[i]);
        for (int i = n; i >= 1; i --) {
            if (p[a[i]]) {
                update(1, n, root[i], root[i+1], p[a[i]], -1);
                update(1, n, root[i], root[i], i, 1);
            } else {
                update(1, n, root[i], root[i+1], i, 1);
            }
            p[a[i]] = i;
        }
        int ans = 0;
        printf("Case #%d:",++kase);
        while (m--) {
            int L, R; scanf("%d%d",&L,&R);
            L = (L + ans) % n + 1;
            R = (R + ans) % n + 1;
            if (L > R) swap(L, R);
            int sum = getsum(L, R, 1, n, root[L]);
            sum = (sum + 1) >> 1;
            ans = query(1, n, root[L], sum);
            printf(" %d",ans);
        }
        printf("\n");
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值