题目大意:

题目链接:​​https://www.luogu.org/problemnew/show/P2801​

给出一串数列,有两个操作:

  • M x y z:M\ x\ y\ z:M x y z:将位置在xxx到yyy之间的数全部加上zzz
  • A x y z:A\ x\ y\ z:A x y z:查询位置在xxx到yyy之间有多少个数不小于zzz

思路:

30分做法:

暴力。

100分做法:

考虑用分块。

对于每次修改操作,如果修改的位置都在一个区间,那么就暴力修改。

如果不在同一个区间,那么就将中间的区间addaddadd数组全部加上zzz,其余两边暴力。

修改很简单吧。

那么对于查询操作,如果查询的位置在同一个区间,暴力。

如果不在同一个区间,可以想到,如果这个区间是单调递增的,那么就可以用二分查找,找到第一个比zzz小的,那么后面的全部比zzz大(或等于)。

那么就在每次修改之后维护另一个数组a[i]a[i]a[i],保证每一个块在aaa数组中都是单调递增的。就可以了。


代码:

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cmath>
#define N 1000100
using namespace std;

int tall[N],pos[N],a[N],L[N],R[N],add[N];
int n,m,t;
char c;

void reset(int x) //重构a数组
{
for (int i=L[x];i<=R[x];i++)
a[i]=tall[i];
sort(a+L[x],a+R[x]+1);
}

int sum(int l,int r,int z) //暴力求个数
{
int ans=0;
for (int i=l;i<=r;i++)
if (tall[i]>=z) ans++;
return ans;
}

int find(int l,int r,int z) //二分求个数
{
int mid,p=r;
while(l<=r)
{
mid=(l+r)/2;
if(a[mid]<z) l=mid+1;
else r=mid-1;
}
return p-l+1;
}

int ask(int l,int r,int z)
{
int q=pos[l],p=pos[r];
if (q==p) return sum(l,r,z-add[q]); //每次z要减去add[这个块],因为这个块整体增加了add[i],所以查找时要减去add[i]
int ans=0;
for (int i=q+1;i<p;i++)
ans+=find(L[i],R[i],z-add[i]);
return ans+sum(l,R[q],z-add[q])+sum(L[p],r,z-add[p]);
}

void change(int l,int r,int z)
{
int q=pos[l],p=pos[r];
if (q==p)
{
for (int i=l;i<=r;i++) tall[i]+=z;
reset(q);
return;
}
for (int i=l;i<=R[q];i++) tall[i]+=z;
for (int i=L[p];i<=r;i++) tall[i]+=z;
reset(q);
reset(p); //重构
for (int i=q+1;i<p;i++)
add[i]+=z;
}

int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%d",&tall[i]);
t=(int)sqrt(n);
for (int i=1;i<=t;i++)
{
L[i]=R[i-1]+1;
R[i]=i*t;
}
if (R[t]<n)
{
t++;
L[t]=R[t-1]+1;
R[t]=n;
}
for (int i=1;i<=t;i++)
{
for (int j=L[i];j<=R[i];j++)
pos[j]=i;
reset(i);
}
int x,y,z;
while (m--)
{
cin>>c;
scanf("%d%d%d",&x,&y,&z);
if (c=='A') printf("%d\n",ask(x,y,z));
else change(x,y,z);
}
return 0;
}