题目
你有一个数列,你要模拟一个类似于快速排序的过程。
有一个固定的数字x。
你要支持三种操作:
- 1 l r 询问区间[l, r]之间的元素的和,也就是
- 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 <= <= 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;
}