bzoj2653middle 主席树+二分答案

来源:互联网 发布:linux 限制用户登录 编辑:程序博客网 时间:2024/05/20 17:27

2653: middle

Time Limit: 20 Sec  Memory Limit: 512 MB
Submit: 1855  Solved: 1031
[Submit][Status][Discuss]

Description

一个长度为n的序列a,设其排过序之后为b,其中位数定义为b[n/2],其中a,b从0开始标号,除法取下整。给你一个
长度为n的序列s。回答Q个这样的询问:s的左端点在[a,b]之间,右端点在[c,d]之间的子序列中,最大的中位数。
其中a<b<c<d。位置也从0开始标号。我会使用一些方式强制你在线。

Input

第一行序列长度n。接下来n行按顺序给出a中的数。
接下来一行Q。然后Q行每行a,b,c,d,我们令上个询问的答案是
x(如果这是第一个询问则x=0)。
令数组q={(a+x)%n,(b+x)%n,(c+x)%n,(d+x)%n}。
将q从小到大排序之后,令真正的
要询问的a=q[0],b=q[1],c=q[2],d=q[3]。  
输入保证满足条件。
第一行所谓“排过序”指的是从大到小排序!

Output

Q行依次给出询问的答案。

Sample Input

5
170337785
271451044
22430280
969056313
206452321
3
3 1 0 2
2 3 1 4
3 1 4 0

271451044
271451044
969056313

Sample Output

HINT

  0:n,Q<=100

1,...,5:n<=2000

0,...,19:n<=20000,Q<=25000


Source


clj的题目果然难啊。。。膜拜各种题解之后写出来的
如果只是求某区间中位数的话考虑可以使用主席树维护权值然后bst一下
然而多了个最大,所以就要二分答案(sb没有想到,基础不够扎实)
二分答案x后如果是定区间只需要验证方案的合法,而动区间则需要构造函数来判断这个值是否可以增大。
显然中位数是相对大小的产物,因此用1代表大于等于x,-1代表小于x,那么对与某个区间的一段转换为1和-1的子区间,如果它的和大于0,显然中位数要更大。很经典的转化方法。
问题到目前为止转化为:给定一个序列,序列中的数均是1或-1,求起始下标为[a,b],终止下标为[c,d]所有子序列中最大的子序列和是否大于0。这显然是一个最大字段和问题。
发现(b,c)是必取的,而最大化的目标可以转化为已b为终止下标在区间[a,b]上的最大字段和和以c为起始下标在区间[c,d]上的最大子段和。这显然对应的是线段树中维护左子区间最大和右子区间最大的做法。
最后一个问题就是,对于不同的x如何维护它对应的线段树。我们发现,如果x转移到x的后继,那么只有一个值x会发生变化。这样就可以直接套用主席树模板。具体来说:先把初始每个数设置为1,然后从小到大吧每个数插入一颗以位置主席树中并将其改成-1
整理一下做法和思路:对于最大的中位数,先构造根据每个数在原区间上的相对大小的序列,及大于等于这个数就为1,否则为-1,这个用主席树维护,然后二分答案x,判断以x为中位数可大还是可小,在主席树上找(b,c)和rm[a,b]和lm[c,d]相加,若大于零则x可能增大,小于零则x必须减小。

一道高级数据结构里蕴含了动态规划与构造性的思想,实在是一道难得的好题。

#include<iostream>#include<cstdlib>#include<cstdio>#include<cstring>#include<algorithm>#include<map>#include<cmath>#define maxn 330000using namespace std;int read(){    char ch = getchar(); int x = 0, f = 1;    while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}    while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}    return x * f;}int pre[maxn], top;struct edge {    int to, next;    void add(int a, int b) {        to = b;        next = pre[a];        pre[a] = top++;    }}e[maxn << 1];void adds(int u, int v){    e[top].add(u, v);    e[top].add(v, u);}int n, m, tot, deep[maxn], in[maxn], out[maxn], dfn[maxn], son[maxn];int sz, ls[maxn * 30], rs[maxn * 30], root[maxn];long long sum[maxn * 30];void dfs(int u, int fa) {in[u] = ++tot; dfn[tot] = u;deep[u] = deep[fa] + 1; son[u] = 0;for(int i = pre[u]; ~i; i = e[i].next) if(e[i].to != fa){dfs(e[i].to, u);son[u] += son[e[i].to] + 1;}out[u] = tot;}void add_tree(int &cur_p, int pre_p, int pos, int val, int L, int R) {sum[cur_p = ++sz] = sum[pre_p] + val;if(L == R) return;int mid = L + R >> 1;if(pos <= mid) {rs[cur_p] = rs[pre_p];add_tree(ls[cur_p], ls[pre_p], pos, val, L, mid);}else {ls[cur_p] = ls[pre_p];add_tree(rs[cur_p], rs[pre_p], pos, val, mid + 1, R);}}void init() {memset(pre, -1, sizeof(pre));n = read(); m = read(); top = 0;for(int i = 1;i < n; ++i) adds(read(), read()); dfs(1, 0);for(int i = 1;i <= n; ++i) add_tree(root[i], root[i - 1], deep[dfn[i]], son[dfn[i]], 1, n);}long long query(int lt, int rt, int st, int ed, int L, int R) {if(L > R) return 0;if(st == L && ed == R) return sum[rt] - sum[lt];int mid = L + R >> 1; long long ans = 0;if(st <= mid) ans += query(ls[lt], ls[rt], st, min(mid, ed), L, mid);if(ed > mid) ans += query(rs[lt], rs[rt], max(mid + 1, st), ed, mid + 1, R);return ans;}void solve() {while(m--) {int p = read(), k = read();long long ans = (long long)min(deep[p] - 1, k) * son[p];ans += query(root[in[p] - 1], root[out[p]], deep[p] + 1, min(deep[p] + k, n), 1, n);printf("%lld\n", ans);}}int main(){init();solve();return 0;}