【SP3261】RACETIME - Race Against Time
蒟蒻的第一道分块题,好激动
一、题意
对于一个序列 \(\{a_n\}\),有两种操作。
- \(a_i\to x\);
- 问 \([l,r]\) 区间中有多少个 \(a_i\le k\)。
二、思路
某位学长和我说:
做题目先看数据范围,这很可能提示了做法。
比如这道题。\(n\le10^5\) 同时 \(Q\le 5\times 10^4\)。
考虑到区间操作,然后数据范围正好符合 \(n\sqrt n\),那我们就可以快乐的使用分块了!
分块是什么?简而言之,优雅的暴力。
考虑原始暴力是如何处理区间操作的?
例如区间加操作,原始的暴力会从 \(l\) 循环到 \(r\),给修改每个数。而分块,将 \(n\) 个数分割成 \(\sqrt n\) 个块,如果整个块都在要加的区间里,打上标记;剩下的一部分不成整块的,使用原始暴力的方法处理。
显然看出,原始暴力为 \(O(n)\),而分块达到了 \(O(\sqrt n)\)!
至于单点修改,直接 \(O(1)\) 处理不就行了。
看本题要求的操作,很容易得到处理方法:
- 先把原序列分块。
- 对每个块排序。
- 单点修改时,将原序列的值改掉,再把这个值所在块覆盖上去,并排序。
- 区间查询时,由于区间有序,可以使用二分优化。
三、代码
#include <math.h>
#include <vector>
#include <stdio.h>
#include <algorithm>
using namespace std;
char opt;
int n,q,cnt,x,y,len,i,j,k,ans,dep,siz;
int l[10000],r[10000];
vector<int> g[10000];
int own[1000005],s[1000005];
int main(){
scanf("%d",&n);
cnt=n/(len=int(sqrt(n)));//小小的压了行,len是块长,cnt是块数。
for(i=1;i<=n;++i)
scanf("%d",s+i);//读入原序列
for(i=1;i<=cnt;++i){//得到每个区间的左端和右端。
l[i]=r[i-1]+1;
r[i]=n/cnt*i;
}
if(r[cnt]!=n){//如果还差一点没有划分,就多加一个块(n/int(sqrt(n))未必整除)。
r[cnt+1]=n;
l[cnt+1]=r[cnt]+1;
++cnt;
}
for(i=1;i<=cnt;++i){
for(j=l[i];j<=r[i];++j){
own[j]=i;
g[i].push_back(s[j]);//收纳入块
}
sort(g[i].begin(),g[i].end());//将每个块排序
}
scanf("%d",&q);
while(q--){
scanf(" %c %d %d",&opt,&x,&y);
if(opt==0){
scanf("%d",&k);
if(own[x]==own[y]){//如果两个在同一个块,直接for循环
ans=0;
for(i=x;i<=y;++i)
s[i]>=k&&++ans;//由于块长为根号n,此处复杂度不超过根号。
printf("%d\n",ans);
}else{
ans=0;
for(i=x;i<=r[own[x]];++i)
s[i]>=k&&++ans;
for(i=l[own[y]];i<=y;++i)//两只小尾巴,同样暴力。
s[i]>=k&&++ans;
for(i=own[x]+1;i<own[y];++i)
ans+=upper_bound(g[i].begin(),g[i].end(),k)-g[i].begin();//lower_bound找第一个大于等于的元素,并返回迭代器
printf("%d\n",ans);
//这一块的复杂度也是根号n
}
}else{//单点修改操作
dep=own[x];
siz=g[dep].size();
s[x]=y;
for(i=0,j=l[dep];i<siz;++i)//推平这个区间
g[dep][i]=s[j++];//此处复杂度根号n
sort(g[dep].begin(),g[dep].end());//此处复杂度log 根号n
}
}
return 0;
}
总复杂度最坏时,就是每次都是区间修改,那么为 \(q\sqrt n+\log \sqrt n\),最好就是每次都查询,为 \(q\sqrt n\),显然能过。
再讲一两个小细节。
- 读入单独一个字符时,使用
getchar()
可能会被\r
搞错,所以是有风险的。可以使用读入字符串取第一个字符或是scanf(" %c",&c)
,这个空格就是让scanf
过滤掉空白字符。效率和getchar()
差不多。 - 迭代器减迭代器返回的是整型,表示这个区间的元素个数。
- 代码中使用
vector
是因为方便,不代表不能用二维数组写。