[BZOJ]2653: middle 线段树合并+二分

29 篇文章 0 订阅
22 篇文章 0 订阅

Description

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

这是一道好题。我们首先可以二分答案,然后判断是否可行。判断的话就把所有比当前二分的答案小的数设为-1,其他数设为1,然后再找区间内的最大连续和,方法是(a,b-1)从右边开始的最大连续和+(b,c)的和+(c+1,d)从左边开始的最大连续和。若这个最大连续和>=0,那么就表示这个数可以作为中位数,否则不行。因为要多次修改询问,我们可以使用主席树或者线段树合并,对于每个可能的答案,我们都建立一棵线段树,因为对于上一棵线段树,只会有一个位置从1变为-1,所以我们只用多建log(n)个节点,就用主席树或者线段树合并搞一搞,维护三个信息:从左边开始的最大连续和、这一段的和、从右边开始的最大连续和。我打了线段树合并,细节有点多,烦。注意编号从0开始。

代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=80010;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x*f;
}
int n,q,b[maxn];
struct tyb{int x,id;}A[maxn];
bool cmp(tyb x,tyb y){return x.x<y.x;}
int cnt=0;
int root[maxn*20],lc[maxn*20],rc[maxn*20];
int sum[maxn*20],ls[maxn*20],rs[maxn*20];
void build(int l,int r)
{
    int t=++cnt;
    sum[t]=ls[t]=rs[t]=r-l+1;
    if(l<r)
    {
        int mid=l+r>>1;
        lc[t]=cnt+1;build(l,mid);
        rc[t]=cnt+1;build(mid+1,r);
    }
}
void pushup(int x)
{
    int L=lc[x],R=rc[x];
    sum[x]=sum[L]+sum[R];
    ls[x]=max(max(ls[L],sum[L]+ls[R]),0);
    rs[x]=max(max(rs[R],sum[R]+rs[L]),0);
}
void link(int &u,int l,int r,int x)
{
    if(!u)u=++cnt;
    if(l==r)
    {
        sum[u]-=2;
        ls[u]--;rs[u]--;
        return;
    }
    int mid=l+r>>1;
    if(x<=mid) link(lc[u],l,mid,x);
    else link(rc[u],mid+1,r,x);
    pushup(u);
}
void merge(int &u1,int u2)
{
    if(!u1){u1=u2;return;}
    if(!u2)return;  
    sum[u1]+=sum[u2];
    int L=lc[u1],R=rc[u1];
    merge(lc[u1],lc[u2]);
    merge(rc[u1],rc[u2]);
    L=lc[u1],R=rc[u1];
    ls[u1]=max(max(ls[L],sum[L]+ls[R]),0);
    rs[u1]=max(max(rs[R],sum[R]+rs[L]),0);
}
int getm(int u,int ll,int rr,int l,int r)
{
    if(l>r)return 0;
    if(l<=ll&&rr<=r)return sum[u];
    int mid=ll+rr>>1;
    if(r<=mid) return getm(lc[u],ll,mid,l,r);
    else if(l>mid) return getm(rc[u],mid+1,rr,l,r);
    else return getm(lc[u],ll,mid,l,mid)+getm(rc[u],mid+1,rr,mid+1,r);
}
int getl(int u,int ll,int rr,int l,int r)
{
    if(l>r)return 0;
    if(l<=ll&&rr<=r){return ls[u];}
    int mid=ll+rr>>1;
    if(r<=mid) return getl(lc[u],ll,mid,l,r);
    else if(l>mid) return getl(rc[u],mid+1,rr,l,r);
    else return max(max(getm(lc[u],ll,mid,l,mid)+getl(rc[u],mid+1,rr,mid+1,r),getl(lc[u],ll,mid,l,mid)),0);
}
int getr(int u,int ll,int rr,int l,int r)
{
    if(l>r)return 0;
    if(l<=ll&&rr<=r)return rs[u];
    int mid=ll+rr>>1;
    if(r<=mid) return getr(lc[u],ll,mid,l,r);
    else if(l>mid) return getr(rc[u],mid+1,rr,l,r);
    else return max(max(getr(lc[u],ll,mid,l,mid)+getm(rc[u],mid+1,rr,mid+1,r),getr(rc[u],mid+1,rr,mid+1,r)),0);
}
bool check(int K,int a,int b,int c,int d)
{
    int rt=root[K-1];
    int lsum=getr(rt,1,n,a,b-1);
    int msum=getm(rt,1,n,b,c);
    int rsum=getl(rt,1,n,c+1,d);
    int Sum=lsum+msum+rsum;
    if(Sum>=0)return true;
    return false;
}
int solve(int a,int b,int c,int d)
{
    int l=1,r=n+1;
    while(l<=r)
    {
        int mid=l+r>>1;
        if(check(mid,a,b,c,d)) l=mid+1;
        else r=mid-1;
    }return l-1;
}
void mem()
{
    memset(root,0,sizeof(root));
    memset(sum,0,sizeof(sum));
    memset(ls,0,sizeof(ls));
    memset(rs,0,sizeof(rs));
}
int main()
{
    mem();
    n=read();
    for(int i=1;i<=n;i++) A[i].x=read(),A[i].id=i;
    sort(A+1,A+1+n,cmp);
    build(1,n);root[0]=1;
    for(int i=1;i<=n;i++)
    {
        link(root[i],1,n,A[i].id);
        merge(root[i],root[i-1]);
    }
    q=read();
    int lastans=0;
    for(int i=1;i<=q;i++)
    {
        int w[6];
        for(int j=1;j<5;j++)w[j]=(read()+lastans)%n+1;
        sort(w+1,w+5);
        lastans=A[solve(w[1],w[2],w[3],w[4])].x;
        printf("%d\n",lastans);
    }
}


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值