[题解] P2824 [HEOI2016/TJOI2016]排序
一道非常巧妙的线段树题 && 思维题
首先题意非常好理解,最暴力的做法无非是根据操作依次排序...然而我没试过多少分啊,可能全wa,毕竟是省选题。
这就是本题的重点:二分
- 二分第 q 个位置的值,把序列中所有大于等于 mid 的全部设成 1 ,小于的设成 0,我们由于并不在乎其他的值是什么,在哪里。
- 所以最后使得 q 恰好等于 1 的mid就是答案。
考虑怎么维护
设计到区间修改,这就是一道非常完美的线段树题。
需要统计区间内 1 的个数
-
对于升序排序:只需要把 1 全放在右边。
-
对于逆序排序:跟升序排序相反。
一道省选难度的题,被我们变成了线段树的板子题。
以后碰到排序的题就需要增加 0/1 序列的意识了。
上代码:
#include <iostream>
#include <cstdio>
#define mid ((l+r)>>1)
#define ls p<<1
#define rs p<<1|1
using namespace std;
const int maxn=1e5 + 10;
int n,m,q;
int T[maxn<<2],tag[maxn<<2];
int a[maxn],op[maxn],L[maxn],R[maxn];
void build(int p,int l,int r,int x){
if(l==r){
T[p]= a[l]>=x;
tag[p]=0;//归零好习惯
return;
}
build(ls,l,mid,x);build(rs,mid+1,r,x);
T[p]=T[ls]+T[rs];tag[p]=0;
}
void pushdown(int p,int l,int r){
if(!tag[p])return;
tag[ls]=tag[rs]=tag[p];
if(tag[p]==1){
T[ls]=mid-l+1,T[rs]=r-mid;
}
else T[ls]=T[rs]=0;
tag[p]=0;
}
int query(int p,int l,int r,int x,int y){
if(x<=l&&r<=y)return T[p];
if(x>r||y<l)return 0;
pushdown(p,l,r);
return query(ls,l,mid,x,y)+query(rs,mid+1,r,x,y);
}
int query_point(int p,int l,int r,int x){
if(l==x&&r==x)return T[p];
pushdown(p,l,r);
if(x<=mid)return query_point(ls,l,mid,x);
else return query_point(rs,mid+1,r,x);
}
void update(int p,int l,int r,int x,int y,int val){
if(x<=l&&r<=y){
T[p]=val*(r-l+1);tag[p]=val?1:-1;
return;
}
if(x>r||y<l)return;
pushdown(p,l,r);
update(ls,l,mid,x,y,val);
update(rs,mid+1,r,x,y,val);
T[p]=T[ls]+T[rs];
}
bool check(int x){
build(1,1,n,x);
for(int i=1;i<=m;i++){
int cnt1=query(1,1,n,L[i],R[i]);
if(op[i]==0){
update(1,1,n,R[i]-cnt1+1,R[i],1);
update(1,1,n,L[i],R[i]-cnt1,0);
}
else{
update(1,1,n,L[i],L[i]+cnt1-1,1);
update(1,1,n,L[i]+cnt1,R[i],0);
}
}
return query_point(1,1,n,q);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",a+i);
for(int i=1;i<=m;i++){
scanf("%d%d%d",op+i,L+i,R+i);
}
scanf("%d",&q);
int ll=1,rr=n,midd,ans;
while(ll<=rr){
midd=(ll+rr)>>1;
if(check(midd))ans=midd,ll=midd+1;
else rr=midd-1;
}
printf("%d",ans);
return 0;
}