题意:
给定一个数组,一共有n个元素,分别为1-n中所有数,给定m次操作,每次给出 ( l , r ) ,表示对区间 ( l , r ) 内的元素进行升序排序或者降序排序,操作结束之后询问数组最中间的那个数是多少。
思路:
一开始看到这道题的时候,毫无思路......是赛后补的题。
题目思路就是对最中间那个数进行二分,二分答案来求解。在 ( 1 , n )区间内进行二分的时候,对于每一个当前值,都用线段树模拟一遍排序过程。
建树的时候将大于等于当前值的点都赋为1,小于当前值的点都赋为0,然后对于线段树中每一个区间,维护一个区间和以及一个延时标记。
对于每一次排序操作的时候,先求出这个区间中所有1的数量,即区间sum和,然后再将左区间全部赋为1或者赋为0,然后再改变右区间。
全部操作结束之后,如果中间值为1,则继续二分右区间,如果为0,则二分左区间,即可求出答案。
上面讲的是操作过程,现在我们来分析一下为什么二分是合理的。
本题合理的原因是,中间的那个数必定是确定的一个数,也就是说,无论你无论你用什么值进行二分,最后形成的01串也只是将 “直接对数组进行排序操作之后” 形成的结果数组,大于的赋为1,小于的赋为0,所以你二分的目的是为了更可能接近那个答案数。
可以发现,当中间数为1的时候,如果你减小二分基准数,则中间数仍旧为1,而答案是出现在中间数恰好在0和1之间转变的那个值,因此当中间数为1时,我们需要扩大二分基准数,由此二分的操作是合理的。
总结:
这是一个新的题型,对于区间排序问题,直接二分出中间值。与此题类似的还有 “区间第K大问题”、“区间排序,多次询问” 等,不过这类题就不能再直接用二分进行操作了,需要一些更高级的数据结构进行维护。
代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define rep(i,a,b) for(int i = a;i <= b;i++)
using namespace std;
const int SIZE = 1e5+100;
struct tree{
int l,r;
int add; //延迟标记,add=0:全是0,add=1:全是1
int sum; //区间和
}t[SIZE*4];
int a[SIZE],n,m;
int ql[SIZE],qr[SIZE];
void build(int p,int l,int r,int base)
{
t[p].l = l, t[p].r = r, t[p].add = -1;
if(l == r)
{
if(a[l] >= base) t[p].sum = 1;
else t[p].sum = 0;
return;
}
int mid = (l+r)>>1;
build(p*2,l,mid,base);
build(p*2+1,mid+1,r,base);
t[p].sum = t[p*2].sum+t[p*2+1].sum;
// printf("build: l:%d,r:%d,sum:%d\n",l,r,t[p].sum);
}
void spread(int p)
{
if(t[p].add == -1 || t[p].l == t[p].r) return;
else if(t[p].add == 0){
t[p*2].sum = 0, t[p*2].add = 0;
t[p*2+1].sum = 0, t[p*2+1].add = 0;
t[p].add = -1;
}
else if(t[p].add == 1){
t[p*2].sum = t[p*2].r-t[p*2].l+1, t[p*2].add = 1;
t[p*2+1].sum = t[p*2+1].r-t[p*2+1].l+1, t[p*2+1].add = 1;
t[p].add = -1;
}
}
void change(int p,int l,int r,int val)
{
if(t[p].l >= l && t[p].r <= r)
{
t[p].add = val;
if(val == 0)
t[p].sum = 0;
else
t[p].sum = t[p].r-t[p].l+1;
return;
}
spread(p);
int mid = (t[p].l+t[p].r)>>1;
if(l <= mid) change(p*2,l,r,val);
if(r > mid) change(p*2+1,l,r,val);
t[p].sum = t[p*2].sum+t[p*2+1].sum;
}
int ask(int p,int l,int r)
{
if(l <= t[p].l && r >= t[p].r)
{
return t[p].sum;
}
int mid = (t[p].l+t[p].r)>>1;
int ans = 0;
spread(p);
if(l <= mid) ans += ask(p*2,l,r);
if(r > mid) ans += ask(p*2+1,l,r);
return ans;
}
int judge(int x)
{
build(1,1,n,x);
rep(i,1,m)
{
int x1 = ql[i], x2 = qr[i], add = 0; //0为升序
if(x1 == x2) continue;
if(x1 > x2){
add = 1;
swap(x1,x2);
}
int tmp = ask(1,x1,x2);
if(add == 0)
{
int pos = x2-tmp;
if(pos >= x1)
change(1,x1,pos,0);
if(x2 >= pos+1)
change(1,pos+1,x2,1);
}
else{
int pos = x1+tmp-1;
if(pos >= x1)
change(1,x1,pos,1);
if(x2 >= pos+1)
change(1,pos+1,x2,0);
}
}
int tmp = ask(1,(n+1)/2,(n+1)/2);
if(tmp == 1) return 1;
else return 0;
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
rep(i,1,n) scanf("%d",&a[i]);
rep(i,1,m) scanf("%d%d",&ql[i],&qr[i]);
int l = 1, r = n;
int ans;
while(l <= r)
{
int mid = (l+r)>>1;
if(judge(mid)) //中间为1
{
// printf("mid:%d,true\n",mid);
ans = mid;
l = mid+1;
}
else{
// printf("mid:%d,false\n",mid);
r = mid-1;
}
}
printf("%d\n",ans);
}
return 0;
}
/*
11 5
7 8 10 4 1 6 5 2 9 3 11
1 5
6 2
3 10
7 4
6 3
*/