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;
}

本文介绍如何利用主席树解决动态序列中位数查询问题。对于给定序列及多次区间查询,通过构建多版本线段树跟踪各元素首次出现位置,实现高效查找指定区间内元素首次出现位置序列的中位数。
491

被折叠的 条评论
为什么被折叠?



