题目大意:
题目链接:https://www.luogu.org/problemnew/show/P2801
给出一串数列,有两个操作:
- 将位置在到之间的数全部加上
- 查询位置在到之间有多少个数不小于
思路:
30分做法:
暴力。
100分做法:
考虑用分块。
对于每次修改操作,如果修改的位置都在一个区间,那么就暴力修改。
如果不在同一个区间,那么就将中间的区间数组全部加上,其余两边暴力。
修改很简单吧。
那么对于查询操作,如果查询的位置在同一个区间,暴力。
如果不在同一个区间,可以想到,如果这个区间是单调递增的,那么就可以用二分查找,找到第一个比小的,那么后面的全部比大(或等于)。
那么就在每次修改之后维护另一个数组,保证每一个块在数组中都是单调递增的。就可以了。
代码:
#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;
}