bzoj 2653: middle (二分+主席树)

2653: middle

Time Limit: 20 Sec   Memory Limit: 512 MB
Submit: 1289   Solved: 730
[ Submit][ Status][ Discuss]

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

5
170337785
271451044
22430280
969056313
206452321
3
3 1 0 2
2 3 1 4
3 1 4 0

271451044
271451044
969056313

Sample Output

HINT

  0:n,Q<=100

  1,...,5:n<=2000

  0,...,19:n<=20000,Q<=25000


Source

[ Submit][ Status][ Discuss]
题解:二分+主席树

每次先二分一个中位数,判断是否可行。

我们考虑如何判断该中位数数是否可行?将序列中的数比当前数小的赋值成-1,大的赋值成1,如果一段区间的和为0,那么该数就是区间的中位数。我们现在考虑[a,b],[c,d]的限制,如果[a,b]中右端连续一段的最大值+(b,c)的区间和+[c,d]中左端连续一段的最大值>=0的话,那么就能说明中位数>=当前二分到的数。

如何维护针对每次数的1,-1序列呢?将数从小到大排序,我们可以初始时令序列为全1的序列,当我们对于一个数建树的时候,我们只需要在前一个数序列的基础上将上一个数的位置改成-1即可。然后就行普通的线段数一样,维护区间总和,区间右端连续、左端连续一段的最大值即可。

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<algorithm>  
#include<cmath>  
#define N 4000003  
using namespace std;  
int a1[N],b[N],sz,n,m,root[N],q[10];  
struct data{  
    int l,r,lx,rx,sum;  
}tr[N];  
int cmp(int x,int y)  
{  
    return a1[x]<a1[y];  
}  
void update(int now)  
{  
    int l=tr[now].l; int r=tr[now].r;  
    tr[now].sum=tr[l].sum+tr[r].sum;  
    tr[now].lx=max(tr[l].lx,tr[l].sum+tr[r].lx);  
    tr[now].rx=max(tr[r].rx,tr[r].sum+tr[l].rx);  
}  
int build(int l,int r)  
{  
    int now=++sz;  
    tr[now].l=tr[now].r=tr[now].lx=tr[now].rx=tr[now].sum=0;  
    if (l==r) {  
        tr[now].sum=tr[now].lx=tr[now].rx=1;  
        return now;  
    }  
    int mid=(l+r)/2;  
    tr[now].l=build(l,mid);  
    tr[now].r=build(mid+1,r);  
    update(now);  
    return now;  
}  
void insert(int &i,int l,int r,int pos,int v)  
{  
    tr[++sz]=tr[i]; i=sz;  
    if (l==r) {  
        tr[i].sum=tr[i].lx=tr[i].rx=v;  
        return;  
    }  
    int mid=(l+r)/2;  
    if (pos<=mid) insert(tr[i].l,l,mid,pos,v);  
    else insert(tr[i].r,mid+1,r,pos,v);  
    update(i);  
    //cout<<i<<" "<<l<<" "<<r<<" "<<tr[i].lx<<" "<<tr[i].rx<<" "<<tr[i].sum<<endl;
}  
int query(int now,int l,int r,int ll,int rr)  
{  
    if (ll>rr) return 0;
    if (ll<=l&&r<=rr) return tr[now].sum;  
    int mid=(l+r)/2;  
    int ans=0;  
    if (ll<=mid) ans+=query(tr[now].l,l,mid,ll,rr);  
    if (rr>mid) ans+=query(tr[now].r,mid+1,r,ll,rr);  
    return ans;  
}  
int findl(int now,int l,int r,int ll,int rr)  
{  
    if (ll>rr) return 0;
    if (ll<=l&&r<=rr) return tr[now].lx;  
    int mid=(l+r)/2; int ans=-1000000000;  
    if (ll<=mid) ans=max(ans,findl(tr[now].l,l,mid,ll,rr));  
    if (rr>mid){
	 int t=query(now,l,r,ll,mid);  
     ans=max(ans,t+findl(tr[now].r,mid+1,r,ll,rr)); 
    }
    return ans;  
}  
int findr(int now,int l,int r,int ll,int rr)  
{ 
    if (ll>rr) return 0; 
    if (ll<=l&&r<=rr) return tr[now].rx;  
    int mid=(l+r)/2; int ans=-1000000000;  
    if (rr>mid) ans=max(ans,findr(tr[now].r,mid+1,r,ll,rr));  
    if (ll<=mid){
	 int t=query(now,l,r,mid+1,rr); 
     ans=max(ans,t+findr(tr[now].l,l,mid,ll,rr)); 
    }
    return ans;  
}  
bool pd(int now,int a,int b,int c,int d)  
{  
    int val=0;  
    val+=query(root[now],0,n-1,b+1,c-1);  //cout<<val<<" ";
    val+=findr(root[now],0,n-1,a,b);  //cout<<val<<" ";
    val+=findl(root[now],0,n-1,c,d);  //cout<<val<<endl;
    return val>=0;  
}  
int solve(int a,int b,int c,int d)  
{  
    int l=0; int r=n-1; int ans=0;  
    while (l<=r){  
        int mid=(l+r)/2;  
        if (pd(mid,a,b,c,d)) ans=mid,l=mid+1;  
        else r=mid-1;  
    }  
    return ans;  
}  
int main()  
{  
    freopen("a.in","r",stdin);  
    freopen("my.out","w",stdout);
    scanf("%d",&n);  
    for (int i=0;i<n;i++) scanf("%d",&a1[i]),b[i]=i;  
    sort(b,b+n,cmp);  
    root[0]=build(0,n-1);  
    for (int i=1;i<n;i++) { 
     root[i]=root[i-1],insert(root[i],0,n-1,b[i-1],-1);
    }
    int ans=0;  
    sort(a1,a1+n);  
    scanf("%d",&m);  
    for (int i=1;i<=m;i++){  
        for (int j=1;j<=4;j++) scanf("%d",&q[j]),q[j]=(q[j]+ans)%n;  
        sort(q+1,q+4+1);  
        int a,b,c,d;  
        a=q[1]; b=q[2]; c=q[3]; d=q[4]; 
		//cout<<a<<" "<<b<<" "<<c<<" "<<d<<endl; 
        ans=solve(a,b,c,d);  
        ans=a1[ans];  
        printf("%d\n",ans);  
    }  
}  



评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值