【Gym - 101234A】Hacker Cups and Balls【线段树 + 二分答案】

题意:

       给定一个数组,一共有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
*/

猜你喜欢

转载自blog.csdn.net/qq_41552508/article/details/82354966