[BZOJ 2653] middle

Link:https://www.lydsy.com/JudgeOnline/problem.php?id=2653

Solution:

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

假设中位数为x,那么把<x的赋值1,把>=x的赋值+1,然后看看是否有连续的一段Sum>=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('\n');
}

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 && b==r) 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 && b==r) 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时的区别

猜你喜欢

转载自www.cnblogs.com/newera/p/9081484.html