P2839 [国家集训队]middle
有一个长度为 n n n的序列,有 m m m次询问,每次询问 a , b , c , d a, b, c, d a,b,c,d,为 l ∈ [ a , b ] , r ∈ [ c , d ] l \in [a, b], r \in [c, d] l∈[a,b],r∈[c,d], [ l , r ] [l, r] [l,r]区间的中位数最大是多少,强制在线, 1 ≤ n ≤ 20000 , 1 ≤ m ≤ 25000 1 \leq n \leq 20000, 1 \leq m \leq 25000 1≤n≤20000,1≤m≤25000。
由于有 a ≤ b ≤ c ≤ d a \leq b \leq c \leq d a≤b≤c≤d,分三个区间讨论 [ a , b ] , [ b + 1 , c − 1 ] , [ c , d ] [a, b], [b + 1, c - 1], [c, d] [a,b],[b+1,c−1],[c,d],其中 [ b + 1 , c − 1 ] [b + 1, c - 1] [b+1,c−1]这个区间是必选的, [ a , b ] , [ c , d ] [a, b], [c, d] [a,b],[c,d]这俩个区间可选可不选,
我们考虑二分答案,对于 m i d ≤ a i mid \leq a_i mid≤ai的我们设置为 1 1 1,否则我们设置为 − 1 -1 −1,对于选定的区间,如果中位数是等于 m i d mid mid的,则一定有区间和 > = 0 >= 0 >=0,
由于我们要使区间中位数尽可能地大,所以我们定然是在 [ a , b ] [a, b] [a,b]上选一个点 p p p在 1 , − 1 1, -1 1,−1序列上使得 [ p , b ] [p, b] [p,b]的和最大,在 [ c , d ] [c, d] [c,d]上选定一个点 q q q也是如此。
如此做的话,单次询问复杂度将会是 O ( n log n log n ) O(n \log n \log n ) O(nlognlogn)的,有没有办法高效来完成呢,整体二分似乎是一个比较好的方法,但是这题要求在线求解,,,
整理一下上面我们所需要的值, [ b + 1 , c − 1 ] [b + 1, c - 1] [b+1,c−1]的区间和, [ a , b ] [a, b] [a,b]区间的后缀最大值, [ c , d ] [c, d] [c,d]区间的前缀最大值,
对于任意一个区间的后缀最大值,前缀最大值都可用线段树维护,同样的区间和也可以用线段树维护,
目前我们所需的就是对于任何一个 m i d mid mid,我们要精确地知道对于某段区间地前缀最大,前缀最小,区间和,
这里就可以考虑用主席树,对每个二分地 m i d mid mid都建立一颗主席树,对于每次二分地 m i d mid mid是否合法,我们只要到对应地主席树上查找即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e4 + 10;
struct Seg {
int l, r, sum;
}t[N << 6];
int root[N], ls[N << 6], rs[N << 6], num;
int a[N], b[N], n, m;
Seg operator + (Seg a, Seg b) {
Seg ans;
ans.sum = a.sum + b.sum;
ans.l = max(a.l, a.sum + b.l);
ans.r = max(b.r, b.sum + a.r);
return ans;
}
void push_up(int rt) {
t[rt] = t[ls[rt]] + t[rs[rt]];
}
void build(int &rt, int l, int r) {
rt = ++num;
if (l == r) {
t[rt] = {-1, -1, -1};
return ;
}
int mid = l + r >> 1;
build(ls[rt], l, mid);
build(rs[rt], mid + 1, r);
push_up(rt);
}
void update(int &rt, int pre, int l, int r, int x) {
rt = ++num, ls[rt] = ls[pre], rs[rt] = rs[pre];
if (l == r) {
t[rt] = {1, 1, 1};
return ;
}
int mid = l + r >> 1;
if (x <= mid) {
update(ls[rt], ls[pre], l, mid, x);
}
else {
update(rs[rt], rs[pre], mid + 1, r, x);
}
push_up(rt);
}
Seg query(int rt, int l, int r, int L, int R) {
if (l >= L && r <= R) {
return t[rt];
}
int mid = l + r >> 1;
if (L <= mid && R > mid) {
return query(ls[rt], l, mid, L, R) + query(rs[rt], mid + 1, r, L, R);
}
else if (L <= mid) {
return query(ls[rt], l, mid, L, R);
}
else {
return query(rs[rt], mid + 1, r, L, R);
}
}
int aa, bb, cc, dd;
bool judge(int x) {
int res = 0;
res += query(root[x], 1, n, aa, bb).r;
if (bb + 1 <= cc - 1) {
res += query(root[x], 1, n, bb + 1, cc - 1).sum;
}
res += query(root[x], 1, n, cc, dd).l;
return res >= 0;
}
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
b[i] = i;
}
scanf("%d", &m);
build(root[n + 1], 1, n);
sort(b + 1, b + 1 + n, [&] (int x, int y) {
return a[x] < a[y];
});
for (int i = n; i >= 1; i--) {
update(root[i], root[i + 1], 1, n, b[i]);
}
int ans = 0;
for (int i = 1; i <= m; i++) {
scanf("%d %d %d %d", &aa, &bb, &cc, &dd);
aa = (aa + ans) % n + 1, bb = (bb + ans) % n + 1;
cc = (cc + ans) % n + 1, dd = (dd + ans) % n + 1;
int tmp[5] = {0, aa, bb, cc, dd};
sort(tmp + 1, tmp + 4 + 1);
aa = tmp[1], bb = tmp[2], cc = tmp[3], dd = tmp[4];
int l = 1, r = n;
while (l < r) {
int mid = l + r + 1 >> 1;
if (judge(mid)) {
l = mid;
}
else {
r = mid - 1;
}
}
ans = a[b[l]];
printf("%d\n", ans);
}
return 0;
}