LGP2801 [] 教主的魔法 学习笔记
LGP2801 [] 教主的魔法 学习笔记
题意简述
给定一个长为 \(n\) 的序列 \(A\)。\(m\) 次操作,每次给出 \(l,r,c\),操作分为两种:区间加、查询区间内有多少数大于 \(c\)。
\(n\le 10^6,m\le 3\times 10^3\)。
做法解析
感觉这个区间内多少数大于 \(c\) 是不是很不可做?枚举肯定会超时,我们大概需要一种区间排序加一个二分的做法?看起来好一点,但是区间排序的复杂度是 \(n\log n\) 的,反而更高。
反思一下,如果我们对于每次询问都把整个询问区间拉出来排序,会有很多冗余的计算量:我们只关注这个区间内元素和 \(C\) 的大小关系,但是把整个区间拉出来排序实际上对区间内元素间的大小关系投入了计算。怎么办?如果把这个区间分成两半排序,那么我们就少考虑了前半区间和后半区间的元素间大小关系——这个信息是对我们解决问题无用的。由此我们就节省了一些计算量。进一步思考:我们如果对这区间分的块越多,冗余的计算就越少啊!

(图为分块排序的合理性)
因此,我们考虑把区间分成 \(B\) 块。对于每一个整块我们维护它内部二分后的结果,散块再暴力处理,这样就行了!(大致思想上就是这样的)
这就是分块的思想:把序列分成若干块,对每一块维护一些信息,操作就变成了对若干整块的统一操作和对两段散块的暴力操作。
做区间加法时,我们对整块打 \(tag\),对散块暴力修改A和B两个数组,时间复杂度 \(\frac{n}{B}+B\)。做询问时,我们对于每个整块二分,散块枚举,时间复杂度为 \(\frac{n}{B}\log B+B\)。当 \(B\) 取 \(\sqrt{n}\) 时我们得到总时间复杂度为 \(O(m\log \sqrt{n}\log \sqrt{n})\),非常优秀。
代码实现
实码棍哥
#include <bits/stdc++.h>
using namespace std;
namespace obasic{
template <typename _T>
void readi(_T &x){
_T k=1;x=0;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')k=-1;
for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-'0';
x*=k;return;
}
template <typename _T>
void writi(_T x){
if(x<0)putchar('-'),x=-x;
if(x>9)writi(x/10);
putchar(x%10+'0');
}
template <typename _T>
int pcedi(_T x,_T y){return (x-1)/y+1;}
};
using namespace obasic;
const int MaxN=1e6+5,MaxNr=1e3+5;
int N,M,Nr,A[MaxN],X,Y,Z;char Opt;
int bsiz,bnum,bel[MaxN],lb[MaxNr],rb[MaxNr];
int B[MaxN],tag[MaxNr];
void fmdf(int cl,int cr,int val){
int bl=bel[cl];
for(int i=cl;i<=cr;i++)A[i]+=val;
for(int i=lb[bl];i<=rb[bl];i++)B[i]=A[i];
sort(B+lb[bl],B+rb[bl]+1);
}
void modify(int cl,int cr,int val){
int bl=bel[cl],br=bel[cr];
if(bl==br){fmdf(cl,cr,val);return;}
for(int i=bl+1;i<br;i++)tag[i]+=val;
fmdf(cl,rb[bl],val),fmdf(lb[br],cr,val);
}
int fqry(int cl,int cr,int val){
int bl=bel[cl],res=0;
for(int i=cl;i<=cr;i++)if(A[i]+tag[bl]>=val)res++;
return res;
}
int query(int cl,int cr,int val){
int bl=bel[cl],br=bel[cr],fres=0;
if(bl==br)return fqry(cl,cr,val);
int sl,sr,smid,sres;
for(int i=bl+1;i<br;i++){
sl=lb[i],sr=rb[i],sres=rb[i]+1;
while(sl<=sr){
smid=(sl+sr)>>1;
if(B[smid]+tag[i]>=val)sres=smid,sr=smid-1;
else sl=smid+1;
}
fres+=rb[i]-sres+1;
}
fres+=fqry(cl,rb[bl],val)+fqry(lb[br],cr,val);
return fres;
}
int main(){
readi(N),readi(M);
bsiz=sqrt(N),bnum=pcedi(N,bsiz);
for(int i=1;i<=bnum;i++)lb[i]=rb[i-1]+1,rb[i]=bsiz*i;
rb[bnum]=N;for(int i=1;i<=N;i++)bel[i]=pcedi(i,bsiz);
for(int i=1;i<=N;i++)readi(A[i]);
memcpy(B,A,sizeof(A));
for(int i=1;i<=bnum;i++)sort(B+lb[i],B+rb[i]+1);
for(int i=1;i<=M;i++){
scanf(" %c",&Opt);
readi(X),readi(Y),readi(Z);
if(Opt=='M')modify(X,Y,Z);
if(Opt=='A')writi(query(X,Y,Z)),puts("");
}
return 0;
}
反思总结
当你觉得某个序列上的某些操作太不可做的时候,想想根号,想想根号,想想根号!!!
想想根号,想想根号,想想根号!!!
浙公网安备 33010602011771号