CCPC-Wannafly Winter Camp Day5 Div2 I.Sorting(线段树-区间重排)

题目

你有一个数列a_1, a_2, \dots, a_n​,你要模拟一个类似于快速排序的过程。

有一个固定的数字x。

你要支持三种操作:

  • 1 l r 询问区间[l, r]之间的元素的和,也就是\sum_{i=l}^r a_i∑i=lr​ai​。
  • 2 l r 对区间[l,r]进行操作,也就是说你把区间中所有的数字拿出来,然后把小于等于x的数字按顺序放在左边,把大于x的数字按顺序放在右边,把这些数字接起来,放回到数列中。比如说x=3,你的区间里的数字是1,5,3,2,4,那么操作完之后区间里面的数字变为1,3,2,5,4。
  • 3 l r 对区间[l,r]进行操作,也就是说你把区间中所有的数字拿出来,然后把大于x的数字按顺序放在左边,把小于等于x的数字按顺序放在右边,把这些数字接起来,放回到数列中。

其中1 <= n,q <= 2e5 , 0 <= x <= 1e9 , 1 <= a_{i} <= 1e9 , 1 <= l <= r <= n

思路来源

归神の例会

题解

注意到,由于x固定,小于等于x的值的相对顺序不会改变

同理,大于x的值的相对顺序也不会改变

那我们只需开两棵线段树,一棵对应小于等于x的树,另一棵对应大于x的树,

记录一下[l,r]区间里有多少个对应的值,

①如果需要重排,就把101010101区间赋值成形如111110000或000011111的样子

1代表是在一棵树里的操作,而0是在另一棵树里的操作,

不容易直接赋的前提下,先统计这段区间里1和0的数量,

然后先区间清零,再在对应左端点或右端点区间赋1

分别预处理小于等于x和大于x的数的前缀和,

②如果需要查询,就先查询[1,l-1]里有多少个小于等于x的数,记为pre1

再查询[l,r]里有多少小于等于x的数,记为num1

[1,pre1]里小于等于x的数已经在前面出现了,

那么[l,r]里小于等于x的数就应该是原序列里第[pre1+1,pre1+num1]这一段

同理大于x的数可以用区间长度作差求出来,

那么只需分别询问前缀和序列里对应一段的值再求和即可

心得

我也是第一次写这种传八个参数的线段树……

不过线段树好就好在,只要思路清晰,下标准确

敲对了实现部分之后,剩下的就是类似套板子的操作了……

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
ll sum1[maxn],cnt1;//小数前缀和  
ll sum2[maxn],cnt2;//大数前缀和 
int tree1[maxn*5],cov1[maxn*5];//小数树 
int tree2[maxn*5],cov2[maxn*5];//大数树 
int c[maxn];
int n,q,x;
int op,l,r;
int pre1,pre2;//[1,l-1]区间里有多少小数和大数
int num1,num2;//[l,r]区间里有多少小数和大数 
void pushup(int a[],int b[],int p)
{
	a[p]=a[p<<1]+a[p<<1|1];
	if((~b[p<<1])&&(~b[p<<1|1]))b[p]=-1;
}
void build(int a[],int b[],int p,int l,int r,int op)
{
	b[p]=-1;//-1表示没有操作,与区间赋0区别 
	if(l==r)
	{
		if(op)a[p]=(c[l]<=x);//小于等于x的树 
		else a[p]=(c[l]>x);//大于x的树 
		return;
	}
	int mid=(l+r)/2;
	build(a,b,p<<1,l,mid,op);
	build(a,b,p<<1|1,mid+1,r,op);
	pushup(a,b,p);
}
void pushdown(int a[],int b[],int p,int l,int r)
{
	if(~b[p])
	{
		int mid=(l+r)/2;
		a[p<<1]=b[p]*(mid-l+1);
		a[p<<1|1]=b[p]*(r-mid);
		b[p<<1]=b[p];
		b[p<<1|1]=b[p];
		b[p]=-1;
	}
}
void update(int a[],int b[],int p,int l,int r,int ql,int qr,int v)//区间赋1或赋0 
{
	if(ql<=l&&r<=qr)
	{
		a[p]=v*(r-l+1);
		b[p]=v;
		return;
	}
	pushdown(a,b,p,l,r); 
	int mid=(l+r)/2;
	if(ql<=mid)update(a,b,p<<1,l,mid,ql,qr,v);
	if(qr>mid)update(a,b,p<<1|1,mid+1,r,ql,qr,v);
	pushup(a,b,p);
}
int ask(int a[],int b[],int p,int l,int r,int ql,int qr)
{
	if(ql<=l&&r<=qr)return a[p];
	pushdown(a,b,p,l,r);
	int mid=(l+r)/2,ans=0;
	if(ql<=mid)ans+=ask(a,b,p<<1,l,mid,ql,qr);
	if(qr>mid)ans+=ask(a,b,p<<1|1,mid+1,r,ql,qr);
	return ans; 
}
int main()
{
	scanf("%d%d%d",&n,&q,&x);
	for(int i=1;i<=n;++i)
	{ 
	 scanf("%d",&c[i]);
	 if(c[i]<=x)sum1[++cnt1]=sum1[cnt1-1]+c[i];
	 else sum2[++cnt2]=sum2[cnt2-1]+c[i]; 
	} 
	build(tree1,cov1,1,1,n,1);
	build(tree2,cov2,1,1,n,0);
	while(q--)
	{
		scanf("%d%d%d",&op,&l,&r);
		if(op==1)
		{
		if(l>1)
		{
			pre1=ask(tree1,cov1,1,1,n,1,l-1);
			pre2=l-1-pre1;
		} 
		else pre1=pre2=0;
		num1=ask(tree1,cov1,1,1,n,l,r);
		num2=(r-l+1)-num1;
		//printf("num1:%d num2:%d sum1:%lld sum2:%lld\n",num1,num2,sum1[num1],sum2[num2]);
		printf("%lld\n",sum1[pre1+num1]-sum1[pre1]+sum2[pre2+num2]-sum2[pre2]);
		}
		else 
		{
			num1=ask(tree1,cov1,1,1,n,l,r);
			num2=(r-l+1)-num1;
			update(tree1,cov1,1,1,n,l,r,0);
			update(tree2,cov2,1,1,n,l,r,0);
		    if(op==2)
		    {
		    	update(tree1,cov1,1,1,n,l,l+num1-1,1);
		    	update(tree2,cov2,1,1,n,l+num1,r,1);
		    }    
		    else
	     	{
	     		update(tree2,cov2,1,1,n,l,l+num2-1,1);
			    update(tree1,cov1,1,1,n,l+num2,r,1);
		    }  
		} 
	}
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/Code92007/article/details/89415560
今日推荐