[BZOJ2653]middle(主席树+二分)

=== ===

这里放传送门

=== ===

题解

这道题最关键的一点就是想到一个对于备选答案的科学判定方法。
给定一个数字 v ,如何判断这个区间的中位数和这个数字的关系?
如果把这个区间内大于这个数字的位置赋值为1,小于这个数字的位置赋值为-1,那么如果这段区间的和小于0,就说明这个区间的中位数比 v 要小;如果区间和等于0,就说明中位数恰好是 v ;如果区间和大于0,就说明中位数比 v 要大。

这说明每次判定一个答案就可以筛掉一部分肯定不合法的答案。那么我们可以二分这个中位数,然后把序列上小于它的数赋值为-1,大于的赋值为1,然后只要能构造出区间和大于等于0的区间就说明答案合法。
我们肯定不能每次二分都重新赋值。但是可以注意到当待判定的答案变大的时候,1的数量单调减小而-1的数量单调增加。并且每个数变成-1以后就不会再变了,一共只会改变n次。那么我们可以用以权值为root数组下标,内层线段树按位置维护的主席树来存储这个序列,每次只需要把恰好从1变成-1的位置修改过来,其余的继承前面的结果就可以了。
查询的话只需要在线段树中维护区间和,靠左的最大和还有靠右的最大和就可以了。

代码

#include
#include
#include
#define inf 1000000000
using namespace std;
int n,tmp[20010],p[20010],v[20010],q,root[20010],cnt,size,anti[20010],lastans;
struct segtree{
    int lMax,rMax,sum,l,r;
    segtree(){lMax=rMax=-inf;sum=l=r=0;}
    void count();
}t[2000010];
void segtree::count(){
    sum=t[l].sum+t[r].sum;
    lMax=max(t[l].lMax,t[l].sum+t[r].lMax);
    rMax=max(t[r].rMax,t[r].sum+t[l].rMax);
}
int comp(int x,int y){return tmp[x]void build(int &i,int l,int r){
    i=++size;t[i]=segtree();
    if (l==r){
        t[i].lMax=t[i].rMax=t[i].sum=1;
        return;
    }
    int mid=(l+r)>>1;
    build(t[i].l,l,mid);
    build(t[i].r,mid+1,r);
    t[i].count();
}
void insert(int &i,int j,int l,int r,int x,int fir){
    if (fir==-1||iif (l==r){
        t[i].lMax=t[i].rMax=t[i].sum=-1;
        return;
    }
    int mid=(l+r)>>1;
    if (x<=mid) insert(t[i].l,t[j].l,l,mid,x,fir);
    else insert(t[i].r,t[j].r,mid+1,r,x,fir);
    t[i].count();
}
segtree query(int i,int l,int r,int left,int right){
    if (i==0||left>right) return segtree();
    if (left<=l&&right>=r) return t[i];
    int mid=(l+r)>>1;
    segtree L=segtree(),R=segtree();
    if (left<=mid) L=query(t[i].l,l,mid,left,right);
    if (right>mid) R=query(t[i].r,mid+1,r,left,right);
    if (L.lMax==-inf) return R;
    if (R.lMax==-inf) return L;
    L.lMax=max(L.lMax,L.sum+R.lMax);
    L.rMax=max(R.rMax,R.sum+L.rMax);
    L.sum+=R.sum;
    return L;
}
bool check(int val,int a,int b,int c,int d){
    segtree L,R,S;
    L=query(root[val-1],1,n,a,b);
    R=query(root[val-1],1,n,c,d);
    S=query(root[val-1],1,n,b+1,c-1);
    return (L.rMax+S.sum+R.lMax)>=0;
}
int divide(int l,int r,int a,int b,int c,int d){
    int mid,ans=0;
    while (l<=r){
        mid=(l+r)>>1;
        if (check(tmp[mid],a,b,c,d)==true){
            ans=max(ans,mid);l=mid+1;
        }else r=mid-1;
    }
    return tmp[ans];
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++){
        scanf("%d",&tmp[i]);p[i]=i;
    }
    sort(p+1,p+n+1,comp);
    tmp[0]=-inf;
    for (int i=1;i<=n;i++){
        if (tmp[p[i]]==tmp[p[i-1]])
          v[p[i]]=cnt;
        else v[p[i]]=++cnt;
        anti[cnt]=tmp[p[i]];
    }
    for (int i=1;i<=n;i++) tmp[i]=v[i];
    sort(tmp+1,tmp+n+1);
    memset(root,-1,sizeof(root));
    build(root[0],1,n);
    for (int i=1;i<=n;i++)
      insert(root[tmp[i]],root[tmp[i]-1],1,n,p[i],root[tmp[i]]);
    scanf("%d",&q);
    cnt=unique(tmp+1,tmp+n+1)-tmp-1;
    for (int i=1;i<=q;i++){
        int Q[5];
        scanf("%d%d%d%d",&Q[1],&Q[2],&Q[3],&Q[4]);
        for (int j=1;j<=4;j++) Q[j]=(Q[j]+lastans)%n+1;
        sort(Q+1,Q+5);
        lastans=anti[divide(1,cnt,Q[1],Q[2],Q[3],Q[4])];
        printf("%d\n",lastans);
    }
    return 0;
}

你可能感兴趣的:(BZOJ,各种二分,主席树算线段树吗)