[BZOJ 2653] middle

Link:

 BZOJ 2653 传送门

Solution:

针对中位数问题的特殊处理:

假设中位数为$x$,那么把小于$x$的赋值−1,把大于等于$x$的赋值+1

然后看看是否有连续的一段$sumge 0$,如有则保证能取到这样的$x$

如果$sum$大于0则证明答案应该更大,相反答案应该更小

$sum$显然是单调的,所以满足二分性质,考虑二分答案。

接下来考虑如何维护区间和,

线段树维护三个值$sum$,$lsum$,$rsum$分别表示区间和,从左端/右端起最大连续子序列和

满足条件$[a,b][c,d]$的最长连续子序列和即为$sum(b,c)+rsum(a,b-1)+lsum(c+1,d)$

但明显不可能对所有的值都单独构建一棵线段树,于是我们想到了主席树

想要有可重用的部分,就要使得建树的顺序具有单调性:

于是我们将原数列排序,先将线段树上所有点都赋为1

然后让第$i$个棵树在第$i-1$棵树的基础上增加一条第$i-1$个点为-1的链即可

 

Code:

#include <bits/stdc++.h>

using namespace std;
#define NOW seg[cur]
#define LC NOW.ls
#define RC NOW.rs

inline int read()
{
    char ch;int num,f=0;
    while(!isdigit(ch=getchar())) f|=(ch=='-');
    num=ch-'0';
    while(isdigit(ch=getchar())) num=num*10+ch-'0';
    return f?-num:num;
}

template<class T> inline void putnum(T x)
{
    if(x<0)putchar('-'),x=-x;
    register short a[20]={},sz=0;
    while(x)a[sz++]=x%10,x/=10;
    if(sz==0)putchar('0');
    for(int i=sz-1;i>=0;i--)putchar('0'+a[i]);
    putchar('
');
}

const int MAXN=1e6;

struct FunTree
{
    int ls,rs;
    int sum,lsum,rsum;
}seg[MAXN];
int n,dat[MAXN],id[MAXN],root[MAXN],cnt=0;

bool cmp(int x,int y)
{
    return dat[x]<dat[y];
}

void Update(int cur)
{
    NOW.sum=seg[LC].sum+seg[RC].sum;
    NOW.lsum=max(seg[LC].lsum,seg[LC].sum+seg[RC].lsum);
    NOW.rsum=max(seg[RC].rsum,seg[RC].sum+seg[LC].rsum);
}

void Build_Tree(int& cur,int l,int r)
{
    cur=++cnt;
    if(l==r){NOW.sum=NOW.lsum=NOW.rsum=1;return;}
    int mid=(l+r)/2;
    Build_Tree(LC,l,mid);Build_Tree(RC,mid+1,r);
    Update(cur);
}

void Insert(int pre,int& cur,int pos,int val,int l,int r)
{
    cur=++cnt;
    if(l==r){NOW.sum=NOW.lsum=NOW.rsum=-1;return;}
    
    int mid=(l+r)>>1;NOW=seg[pre];
    if(pos<=mid) Insert(seg[pre].ls,NOW.ls,pos,val,l,mid);
    else Insert(seg[pre].rs,NOW.rs,pos,val,mid+1,r);
    Update(cur);
}

int Query(int a,int b,int cur,int l,int r)
{
    if(a<=l && r<=b) return NOW.sum;
    int mid=(l+r)>>1,ret=0;
    
    if(a<=mid) ret+=Query(a,b,NOW.ls,l,mid);
    if(b>mid) ret+=Query(a,b,NOW.rs,mid+1,r);
    return ret; 
}

int Left(int a,int b,int cur,int l,int r)  //注意求Left和Right时与求Sum的不同
{
    if(a<=l && r<=b) return NOW.lsum;
    
    int mid=(l+r)>>1;
    if(b<=mid) return Left(a,b,NOW.ls,l,mid);
    if(a>mid) return Left(a,b,NOW.rs,mid+1,r);
    return max(Left(a,mid,NOW.ls,l,mid),Query(a,mid,NOW.ls,l,mid)+Left(mid+1,b,NOW.rs,mid+1,r));
}

int Right(int a,int b,int cur,int l,int r)
{
    if(a<=l && r<=b) return NOW.rsum;
    
    int mid=(l+r)>>1;
    if(a>mid) return Right(a,b,NOW.rs,mid+1,r);
    if(b<=mid) return Right(a,b,NOW.ls,l,mid);
    return max(Right(mid+1,b,NOW.rs,mid+1,r),Query(mid+1,b,NOW.rs,mid+1,r)+Right(a,mid,NOW.ls,l,mid));
}

bool check(int x,int a,int b,int c,int d)
{
    int ret1=Query(b,c,root[x],1,n);
    int ret2=max(Left(c+1,d,root[x],1,n),0);
    int ret3=max(Right(a,b-1,root[x],1,n),0);
    return ret1+ret2+ret3>=0;
}

int main()
{
    n=read();
    for(int i=1;i<=n;i++) dat[i]=read(),id[i]=i;
    sort(id+1,id+n+1,cmp);sort(dat+1,dat+n+1);
    Build_Tree(root[1],1,n);
    
    for(int i=2;i<=n;i++) Insert(root[i-1],root[i],id[i-1],-1,1,n);
    
    int T=read(),last=0;
    while(T--)
    {
        int q[5];
        for(int i=1;i<=4;i++)
            q[i]=(read()+last)%n+1;
        sort(q+1,q+5);
        
        int l=1,r=n;
        while(l<=r) //二分答案
        {
            int mid=(l+r)>>1;
            if(check(mid,q[1],q[2],q[3],q[4])) l=mid+1;
            else r=mid-1;
        }
        putnum(last=dat[r]);
    }
    return 0;
}

Review:

1、针对中位数的套路:

把小于$x$的赋值−1,把大于等于$x$的赋值+1,然后看看是否有连续的一段$Sum$>=0

  

2、主席树建树时要使其顺序具有一定单调性

3、注意对查找$Left$、$Right$时和查找$Sum$时的区别

原文地址:https://www.cnblogs.com/newera/p/9081484.html