BZOJ 2653 middle

title: ‘BZOJ 2653 middle’
categories: BZOJ
date: 2016-1-6 19:30:00
tags: [主席树,二分]


Description

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

Input

第一行序列长度n。
接下来n行按顺序给出a中的数。
接下来一行Q。
然后Q行每行a,b,c,d,我们令上个询问的答案是x(如果这是第一个询问则x=0)。
令数组q={(a+x)%n,(b+x)%n,(c+x)%n,(d+x)%n}。
将q从小到大排序之后,令真正的要询问的a=q[0],b=q[1],c=q[2],d=q[3]。
输入保证满足条件。

Output

Q行依次给出询问的答案。

Sample

input.txt
5
170337785
271451044
22430280
969056313
206452321
3
3 1 0 2
2 3 1 4
3 1 4 0

output.txt
271451044
271451044
969056313

Hint

0:n,Q<=100
1,…,5:n<=2000
0,…,19:n<=20000,Q<=25000

Solution

这道题真的挺难想的= =
我想了好久没想出来,然后又看题解看了好久,毕竟太菜。。。(orz神犇zyj随意秒了,无情d我)
要求中位数最大,而且区间端点要在两个给定的区间内,我们考虑二分这个中位数,然后转化成判定问题。
那么怎么判定某段区间内某个数x能不能作为中位数?
对于区间内的数,凡是>=x的数都标为1,否则标为-1,然后求出这个区间的最大子段和。若sum>=0,则该数肯定可以成为这个区间的中位数。求最大子段和,可以通过在线段树里维护一下lmax和rmax来求得,类似于里的操作。由于要求两端点分别在[a,b],[c,d]中,因此最大子段和就是 T[a,b]rmax+T[b+1,c1]sum+T[c,d]lmax
线段树不可能建很多棵,主席树是很好的解决方案。我们发现大小相邻的两个数的线段树只相差一处地方,于是可以先对数字排序,然后按大小依次建树,初始所有的点都为1,然后依次修改为-1即可。
还有一个问题,要是我们二分出的中位数不在我们选中的区间内怎么办?
若二分出的数不在区间内且判定成功了,就说明区间内绝对有一个更大的符合要求的数,可以直接继续向上二分。

Code

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

#define maxn 20000+5
#define maxp 4000000+5

using namespace std;

struct Ha_Tree{
    int l,r;
    int lx,rx,sum;
}tr[maxp];

int root[maxn],ori[maxn],pos[maxn];
int n,m,ind;

bool cmp(int s,int b){
    return ori[s]<ori[b];
}

void Update(int k){
    int l=tr[k].l,r=tr[k].r;
    tr[k].sum=tr[l].sum+tr[r].sum;
    tr[k].lx=max(tr[l].lx,tr[l].sum+tr[r].lx);
    tr[k].rx=max(tr[r].rx,tr[r].sum+tr[l].rx);
}

void Modify(int l,int r,int last,int &k,int pos,int val){
    k=++ind;
    tr[k]=tr[last];
    if(l==r){
        tr[k].sum=tr[k].lx=tr[k].rx=val;
        return;
    }
    int mid=(l+r)>>1;    
    if(pos<=mid) Modify(l,mid,tr[last].l,tr[k].l,pos,val);
    else Modify(mid+1,r,tr[last].r,tr[k].r,pos,val);
    Update(k);
}

int Query_all(int k,int l,int r,int pl,int pr){
    if(l==pl && r==pr) return tr[k].sum;
    int mid=(l+r)>>1;
    if(pr<=mid) return Query_all(tr[k].l,l,mid,pl,pr);
    else if(pl>mid) return Query_all(tr[k].r,mid+1,r,pl,pr);
    else return Query_all(tr[k].l,l,mid,pl,mid)+Query_all(tr[k].r,mid+1,r,mid+1,pr);
}

int Query_l(int k,int l,int r,int pl,int pr){
    if(l==pl && r==pr) return tr[k].lx;
    int mid=(l+r)>>1;
    if(pr<=mid) return Query_l(tr[k].l,l,mid,pl,pr);
    else if(pl>mid) return Query_l(tr[k].r,mid+1,r,pl,pr);
    else return max(Query_l(tr[k].l,l,mid,pl,mid),Query_all(tr[k].l,l,mid,pl,mid)+Query_l(tr[k].r,mid+1,r,mid+1,pr));
}

int Query_r(int k,int l,int r,int pl,int pr){
    if(l==pl && r==pr) return tr[k].rx;
    int mid=(l+r)>>1;
    if(pr<=mid) return Query_r(tr[k].l,l,mid,pl,pr);
    else if(pl>mid) return Query_r(tr[k].r,mid+1,r,pl,pr);
    else return max(Query_r(tr[k].r,mid+1,r,mid+1,pr),Query_all(tr[k].r,mid+1,r,mid+1,pr)+Query_r(tr[k].l,l,mid,pl,mid));
}

bool check(int k,int a,int b,int c,int d){
    int res=0;
    if(b+1!=c) res+=Query_all(root[k],1,n,b+1,c-1);
    res+=Query_r(root[k],1,n,a,b);
    res+=Query_l(root[k],1,n,c,d);
    if(res>=0) return true;
    return false;
}

int find(int a,int b,int c,int d){
    int l=1,r=n;
    while(l<r){
        int mid=(l+r+1)>>1;
        if(check(mid,a,b,c,d)) l=mid;
        else r=mid-1;
    }
    return ori[pos[l]];
}

void Build(int l,int r,int &k){
    k=++ind;
    if(l==r){
        tr[k].lx=tr[k].rx=tr[k].sum=1;
        return;
    }
    int mid=(l+r)>>1;
    Build(l,mid,tr[k].l);
    Build(mid+1,r,tr[k].r);
    Update(k);
}

int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&ori[i]),pos[i]=i;
    sort(pos+1,pos+1+n,cmp);
    Build(1,n,root[1]);
    for(int i=2;i<=n;i++)
        Modify(1,n,root[i-1],root[i],pos[i-1],-1);
    scanf("%d",&m);
    int x=0;
    for(int i=1;i<=m;i++){
        int q[4];
        scanf("%d%d%d%d",&q[0],&q[1],&q[2],&q[3]);
        for(int i=0;i<4;i++) q[i]=(q[i]+x)%n+1;
        sort(q,q+4);
        printf("%d\n",x=find(q[0],q[1],q[2],q[3]));
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值