洛谷:[国家集训队]middle(主席树 + 二分)

题目描述

一个长度为n的序列a,设其排过序之后为b,其中位数定义为b[n/2],其中a,b从0开始标号,除法取下整。

给你一个长度为n的序列s。

回答Q个这样的询问:s的左端点在[a,b]之间,右端点在[c,d]之间的子序列中,最大的中位数。

其中a<b<c<d。

位置也从0开始标号。

我会使用一些方式强制你在线。

输入输出格式

输入格式:

 

第一行序列长度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]。

输入保证满足条件。

 

输出格式:

 

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

 

输入输出样例

输入样例#1: 复制

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

输出样例#1: 复制

271451044
271451044
969056313

说明

0:n,Q<=100

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

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

题意:给N个数,Q个询问,每个询问给abcd,求[a,b]选起点和[c,d]选终点的子序列中中位数的最大值。

思路:中位数首先考虑二分答案,比它小和大的设为-1和1,取sum为[b+1,c-1]的和加上[a,b]的最大后缀加上[c,d]的最大前缀,判断sum是否>=0,但是这样每个询问复杂度都是O(nlogn)不能接受,于是用主席树预处理,就能省去设置1和-1这一步,最终单个询问复杂度为O(logn*logn)。

# include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+30;
struct node{
    int num, id;
}a[maxn];
struct node2{
    int lx, rx, tot;
}tree[maxn<<4];
int root[maxn]={0}, ls[maxn<<4], rs[maxn<<4], cnt=0, n;
bool cmp(node x, node y){
    return x.num < y.num;
}
node2 merge(node2 x, node2 y){
    node2 tmp;
    tmp.tot = x.tot + y.tot;
    tmp.lx = max(x.tot+y.lx, x.lx);
    tmp.rx = max(y.tot+x.rx, y.rx);
    return tmp;
}
void build(int &rt, int l, int r){
    int tmp = rt; rt = ++cnt; tree[rt] = tree[tmp];
    ls[rt] = ls[tmp]; rs[rt] = rs[tmp];
    if(l == r){
        tree[rt].lx = tree[rt].rx = tree[rt].tot = 1;
        return;
    }
    int mid = l+r>>1;
    build(ls[rt], l, mid);
    build(rs[rt], mid+1, r);
    tree[rt] = merge(tree[ls[rt]], tree[rs[rt]]);
}
void update(int &rt, int l, int r, int pos){
    int tmp = rt; rt = ++cnt; tree[rt] = tree[tmp];
    ls[rt] = ls[tmp]; rs[rt] = rs[tmp];
    if(l == r){
        tree[rt].lx = tree[rt].rx = tree[rt].tot = -1;
        return;
    }
    int mid = l+r>>1;
    if(pos <= mid) update(ls[rt], l, mid, pos);
    else update(rs[rt], mid+1, r, pos);
    tree[rt] = merge(tree[ls[rt]], tree[rs[rt]]);
}
node2 query(int rt, int l, int r, int L, int R){
    if(L <= l && R >= r) return tree[rt];
    int mid = l+r>>1;
    if(R <= mid) return query(ls[rt], l, mid, L, R);
    if(L > mid) return query(rs[rt], mid+1, r, L, R);
    else return merge(query(ls[rt], l, mid, L, R), query(rs[rt], mid+1, r, L, R));
}
bool check(int id, int l1, int r1, int l2, int r2){
    int sum = 0;
    if(r1+1 < l2) sum += query(root[id], 1, n, r1+1, l2-1).tot;
    sum += query(root[id], 1, n, l1, r1).rx;
    sum += query(root[id], 1, n, l2, r2).lx;
    return sum >= 0;
}
int main(){
    int q, c[4];
    scanf("%d",&n);
    for(int i=1; i<=n; ++i) scanf("%d",&a[i].num), a[i].id = i;
    sort(a+1, a+1+n, cmp);
    build(root[1], 1, n);
    for(int i=2; i<=n; ++i){
        root[i] = root[i-1];
        update(root[i], 1, n, a[i-1].id);
    }
    int ans = 0;
    scanf("%d",&q);
    while(q--){
        for(int i=0; i<4; ++i) scanf("%d",&c[i]), c[i]=(c[i]+ans)%n;
        sort(c, c+4);
        for(int i=0; i<4; ++i) ++c[i];
        int l = 1, r = n;
        while(l <= r){
            int mid = l+r>>1;
            if(check(mid, c[0], c[1], c[2], c[3])) l = mid+1;
            else r = mid-1;
        }
        printf("%d\n",a[r].num);
        ans = a[r].num;
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值