[主席树+二分] BZOJ2653: middle

本文介绍了一种使用主席树解决动态子序列中位数最大值查询问题的方法。通过预处理和二分查找,实现了高效查询。适用于需要频繁进行区间中位数计算的场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

给定一个数字序列。
多次询问,每次求出左端点为a~b,右端点为c~d的所有子序列的中位数的最大值。
一个序列的中位数:
一个长度为n的序列a,设其排过序之后为b,其中位数定义为b[n/2],其中a,b从0开始标号,除法取下整。

题解

很强的主席树应用。
可以想到二分答案,如何验证呢?
肯定算一下比当前答案mid大的数的个数和小的数的个数差。我们设大于等于mid的数为1,小于mid的数为-1。我们要求一次合法范围的最大连续子段和即可。
若大于等于0,则说明有可能是最优中位数,否则不可能。
现在我们的问题就是:对于某个数mid,如何快速地把序列中的数按关于mid的大小记为1或-1,且还能在这个1/-1序列中做最大连续子段和与区间和操作。
如何做呢?感觉肯定要预处理一些东西,不然没法搞。
先把数字离散,发现mid的取值只有n种。
我们就可以建n个版本的线段树来表示不同的mid对应的1/-1序列。
而且可以注意到有一个关键点:第mid棵树相对于第mid-1棵,仅仅改变了若干个值为mid-1的位置。
分析到这里就可以主席树啦!n棵树构造完后,每次询问在对应的树里二分即可。
时间复杂度:O(nlog22n)

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=45005;
int n,Q,a[maxn],pos[maxn],len;
pair<int,int> b[maxn];
struct info{
    int sum,ml,mr;
    info(int sum=0,int ml=0,int mr=0):sum(sum),ml(ml),mr(mr){}
};
info merge(info A,info B){
    info c;
    c.sum=A.sum+B.sum;
    c.ml=max(A.ml,A.sum+B.ml); c.mr=max(B.mr,B.sum+A.mr);
    return c;
}
struct node{
    int L,R; info w;
    node* ch[2];
    void maintain(){ w=merge(ch[0]->w,ch[1]->w); }
} base[maxn*20], *top=base, nil, *null=&nil, *rt[maxn];
node* newnode(int L=0,int R=0){
    top->L=L; top->R=R; top->w=info(1,1,1);
    top->ch[0]=top->ch[1]=null;
    return top++;
}
node* build(int L,int R){
    node* p=newnode(L,R);
    if(L==R) return p;
    int mid=(L+R)>>1;
    p->ch[0]=build(L,mid); p->ch[1]=build(mid+1,R);
    p->maintain(); return p;
}
node* Updata(node* pre,int val){
    node* p=newnode(); *p=*pre;
    if(p->L==p->R){
        p->w=info(-1,0,0); 
        return p;
    }
    int mid=(p->L+p->R)>>1;
    if(val<=mid) p->ch[0]=Updata(p->ch[0],val);
            else p->ch[1]=Updata(p->ch[1],val);
    p->maintain(); return p;
}
info Query(node* p,int L,int R){
    if(p->R<L||R<p->L) return info(0,0,0);
    if(L<=p->L&&p->R<=R) return p->w;
    if(p->L==p->R) return info(0,0,0);
    return merge(Query(p->ch[0],L,R),Query(p->ch[1],L,R));
}
int q[4];
bool check(int now){
    node* R=rt[pos[now]];
    return (Query(R,q[0],q[1]-1).mr+Query(R,q[1],q[2]).sum+Query(R,q[2]+1,q[3]).ml)>=0;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]), b[++len]=make_pair(a[i],i);
    sort(b+1,b+1+len);
    rt[1]=build(1,n); pos[pos[0]=1]=1; int len_rt=1; //rt[i]:前1~i-1为-1的版本 
    for(int i=1,j;i<=len;j++){
        j=i;
        while(j<=len&&b[i].first==b[j].first) rt[len_rt+1]=Updata(rt[len_rt],b[j++].second), len_rt++;
        pos[++pos[0]]=len_rt; //pos[i]:第i大的数作为中位数的版本的根 
        i=j;
    }
    for(int i=1;i<=len;i++) b[i].second=0; len=unique(b+1,b+1+len)-(b+1); //去重 
    scanf("%d",&Q);
    int printed=0;
    while(Q--){
        scanf("%d%d%d%d",&q[0],&q[1],&q[2],&q[3]);
        for(int i=0;i<=3;i++) q[i]=(q[i]+printed)%n+1; sort(q,q+4);
        int L=1,R=len,res;
        while(L<=R){
            int mid=(L+R)>>1;
            if(check(mid)) L=mid+1, res=mid;
                      else R=mid-1;
        }
        printf("%d\n",printed=b[res].first);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值