读题
首先,形式化的题意:
有一个 N N N 个数的数列 a a a,存在两种操作:
- 将区间 [ l , r ] [l,r] [l,r] 中所有的数加上 w w w。
- 统计区间 [ l , r ] [l,r] [l,r] 中大于 c c c 的数的个数。
可以使用分块算法求解。
分块
核心思想:将长度为 n n n 的数组分成 ceil( n k ) \text{ceil(}\frac n k\text{)} ceil(kn) 块,每块长度为 k k k。
那么我们将分块思想运用在题目中:
思路
- 对于操作1:分两种情况
-
- l l l 和 r r r 在同一块中时,暴力修改即可。最坏时间复杂度为 O ( k ) \text{O}(k) O(k)。
-
- l l l 和 r r r 不在同一块中,此时对于 l l l, r r r 所在块暴力修改。其余块使用 delta \text{delta} delta 标记,统一加上 w w w。时间复杂度亦为 O ( k ) \text{O}(k) O(k)。
-
- 对于操作二同理:
- l l l 和 r r r 在同一块中时,暴力查询即可。最坏时间复杂度为 O ( n k ) \text{O}(\frac{n}{k}) O(kn)。
-
l
l
l 和
r
r
r 不在同一块中,此时对于
l
l
l,
r
r
r 所在块暴力查询。其余块排序,使用
lower_bound
统计。时间复杂度为 O ( n k log k ) \text{O}(\frac{n}{k}\log k) O(knlogk)。
OI-wiki
说:利用均值不等式可知,当
n
k
=
k
\dfrac{n}{k}=k
kn=k ,即
k
=
n
k=\sqrt n
k=n 时,单次操作的时间复杂度最优。
所以,总时间复杂度为 O ( max ( n , n log n ) ) \text{O}(\max(n,\sqrt n\log \sqrt n)) O(max(n,nlogn))。
AC code \texttt{AC code} AC code
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N=1000005;
int n,q,cnt;
int a[N];
int t[N];
int st[N],ed[N],siz[N],belong[N];
int delta[N],sum[N];
void Sort(int blockId) {
for(int i=st[blockId];i<=ed[blockId];i++)
t[i]=a[i];
sort(t+st[blockId],t+ed[blockId]+1);
}
int Query(int l,int r,int c) {
int ans=0;
if(belong[l]==belong[r]) {
for(int i=l;i<=r;i++) {
ans+=(a[i]+delta[belong[i]]>=c);
}
return ans;
}
for(int i=l;i<=ed[belong[l]];i++)
ans+=(a[i]+delta[belong[l]]>=c);
//printf("s1:%d\n",ans);
for(int i=belong[l]+1;i<=belong[r]-1;i++)
ans+=ed[i]-(lower_bound(t+st[i],t+ed[i]+1,c-delta[i])-t)+1;
//printf("s2:%d\n",ans);
for(int i=st[belong[r]];i<=r;i++)
ans+=(a[i]+delta[belong[r]]>=c);
//printf("s3:%d\n",ans);
return ans;
}
void Modify(int l,int r,int w) {
if(belong[l]==belong[r]) {
for(int i=l;i<=r;i++) a[i]+=w;
Sort(belong[l]);
return ;
}
for(int i=l;i<=ed[belong[l]];i++) a[i]+=w;
for(int i=st[belong[r]];i<=r;i++) a[i]+=w;
for(int i=belong[l]+1;i<=belong[r]-1;i++) delta[i]+=w;
Sort(belong[l]);
Sort(belong[r]);
}
int main() {
cin>>n>>q;
for(int i=1;i<=n;i++) {
cin>>a[i];
t[i]=a[i];
}
cnt=sqrt(n);
for(int i=1;i<=cnt;i++) {
st[i]=n/cnt*(i-1)+1;
ed[i]=n/cnt*i;
}
ed[cnt]=n;
for(int i=1;i<=cnt;i++) {
for(int j=st[i];j<=ed[i];j++)
belong[j]=i;
siz[i]=ed[i]-st[i]+1;
Sort(i);
}
char op;
int l,r,c;
for(int i=1;i<=q;i++) {
cin>>op>>l>>r>>c;
switch(op) {
case 'A':
printf("%d\n",Query(l,r,c));
break;
case 'M':
Modify(l,r,c);
break;
}
}
return 0;
}
Tips:
belong i \text{belong}_i belongi 为 i i i 属于的块编号。
st i , ed i , siz i \text{st}_i,\text{ed}_i, \text{siz}_i sti,edi,sizi 为 i i i 所在的块的起点,终点,大小。
a i \text{a}_i ai 原数组。
t i \text t_i ti 排序后数组。