BZOJ 2653 可持久化线段树

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

对于中位数有一个性质,将所有大于等于他的数置为 1 , 小于他的数置为1,则所有数的和 0 1。单调性还是很显然的,因此我们二分答案,若对数组求和答案大于等于 0 ,则当前答案可行。

定长的区间我们已经会求解中位数了,现在的问题是,左端点在[l1,r1],右端点在 [l2,r2] , 如何找到中位数。此时我们二分一个答案,将数组所有数按之前的规则变成 1 1
会发现若上述”左端点不固定的若干区间” + “定区间” + “右端点不定的若干区间”组成的新区间的最大子段和大于等于 0 ,则我们二分出的答案必然是某个区间的中位数。

二分答案,以及如何判定这个答案是否满足每个询问区间的方法我们已经知道了。
现在的问题就是,如何快速得到一个区间的最大子段和,这是一个经典的线段树问题。

其实我们还剩下最后一个问题,每次二分出一个答案,所有数组都在变化,总不能每个询问都建立log个线段树。

其实只要想一下就会发现,这题中出现的数组的种类只有n+1种(初始所有数都是 1 ,每次插入一个数,都一定会使这个数组中比他小的数变成1)。

如果从小到大插入数的话,其实相对上一次插入只会使得一个数变化。因此可以考虑用可持久化线段树来完成动态修改过程。
复杂度 O(nlognlogn) .

#include <bits/stdc++.h>
#define all(x) x.begin(), x.end()
using namespace std;

const int maxn = 22000;

int n, cid;
int val[maxn];
vector<pair<int, int> > V;
struct Seg { int l, r, sum, lsum, rsum;} tr[maxn*40];
int root[maxn];
int ans = 0;

void pushup(int x) {
    tr[x].sum = tr[tr[x].l].sum + tr[tr[x].r].sum;
    tr[x].lsum = max(tr[tr[x].l].lsum, tr[tr[x].l].sum + tr[tr[x].r].lsum);
    tr[x].rsum = max(tr[tr[x].r].rsum, tr[tr[x].r].sum + tr[tr[x].l].rsum);
}

int build(int l, int r) {
    int x = ++ cid;
    if(l == r) {
        tr[x].sum = tr[x].lsum = tr[x].rsum = 1;
        return x;
    }
    int m = l + r >> 1;
    tr[x].l = build(l, m);
    tr[x].r = build(m+1, r);
    pushup(x);
    return x;
}

int update(int y, int l, int r, int pos, int val) {
    int x = ++cid;
    if(l == r) {
        tr[x].sum = tr[x].rsum = tr[x].lsum = val;
        return x;
    }
    int m = l + r >> 1;
    if(pos <= m) {
        tr[x].r = tr[y].r;
        tr[x].l = update(tr[y].l, l, m, pos, val);
    } else {
        tr[x].l = tr[y].l;
        tr[x].r = update(tr[y].r, m+1, r, pos, val);
    }
    pushup(x);
    return x;
}

int ask(int L, int R, int l, int r, int x) {
    if(L > R) return 0;
    if(L <= l && R >= r){
        return tr[x].sum;
    }
    int m = (l + r) >> 1;
    int res = 0;
    if(L <= m)
        res += ask(L, R, l, m, tr[x].l);
    if(R > m)
        res += ask(L, R, m+1, r, tr[x].r);
    return res;
}

int askl(int L, int R, int l, int r, int x) {
    if(L > R) return 0;
    if(L <= l && R >= r) return tr[x].lsum;
    int m = (l + r) >> 1;
    if(R <= m)
        return askl(L, R, l, m, tr[x].l);
    else if(L > m)
        return askl(L, R, m+1, r, tr[x].r);
    else
        return max(askl(L, R, l, m, tr[x].l), ask(L, R, l, m, tr[x].l) + askl(L, R, m+1, r, tr[x].r));
}

int askr(int L, int R, int l, int r, int x) {
    if(L > R) return 0;
    if(L <= l && R >= r)
        return tr[x].rsum;
    int m = (l + r) >> 1;
    if(R <= m)
        return askr(L, R, l, m, tr[x].l);
    else if(L > m)
        return askr(L, R, m+1, r, tr[x].r);
    else
        return max(askr(L, R, m+1, r, tr[x].r), askr(L, R, l, m, tr[x].l) + ask(L, R, m+1, r, tr[x].r));
}

void init() {
   cid = 0;
}

bool check(int x, int l1, int r1, int l2, int r2) {
    int cnt = 0;
    cnt += ask(r1+1, l2-1, 0, n-1, root[x]);
    cnt += askr(l1, r1, 0, n-1, root[x]);
    cnt += askl(l2, r2, 0, n-1, root[x]);
    return cnt >= 0;
}

int main() {
    init();
    scanf("%d", &n);
    for(int i = 0; i < n; i++) {
        scanf("%d", &val[i]);
        V.push_back({val[i], i});
    }
    sort(all(V));
    root[0] = build(0, n-1);
    for(int i = 1; i <= n; i++)
        root[i] = update(root[i-1], 0, n-1, V[i-1].second, -1);
    int q;
    scanf("%d", &q);
    while(q--) {
        vector<int> tp;
        for(int i = 1; i <= 4; i++) {
            int v;
            scanf("%d", &v);
            v += ans, v %= n;
            tp.push_back(v);
        }
        sort(all(tp));
        int l = 1, r = n + 1, m;
        while(l < r) {
            m = l + r >> 1;
            if(check(m, tp[0], tp[1], tp[2], tp[3])) l = m + 1;
            else r = m;
        }
        printf("%d\n", ans = V[l-1].first);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值