归纳(三):分块
何为分块
优雅的暴力
思维难度低下,代码难度低下,非常优秀的一种算法(?)。
实现方法主要是块与块之间 \(O(1)*\sqrt{n}\) 查询,边角 \(O(\sqrt{n})\) 暴力查询。
总复杂度 \(O(\sqrt{n})\)。
代码实现
首先需要块的大小 \(block\) ,和每个下标归属于哪个块 \(belong[i]\) 。
如果需要块内有序,可以使用 \(std::vector\) 。
区间修改对于整块使用lazy标记的思想,边边角角还是\(O(\sqrt{n})\)暴力。
查询同理。
例题(一):教主的魔法
可以说,这是分块最最经典的一道题了。
因为似乎没有其他做法?
分好块,用 \(std::vector<int> vc[maxn]\) 维护区间的高矮关系。
用 \(std::lower_bound\) 进行查询。
修改就打标记。
#include<cstdio>
#include<vector>
#include<cmath>
#include<algorithm>
const int maxn=1e6+5;
class Divid_Block {
private:
int n,q,delta[maxn],a[maxn];
int block,belong[maxn],num;
std::vector<int> vc[maxn];
void update(int x) {
vc[x].clear();
for(int i=(x-1)*block+1;i<=x*block;i++)
vc[x].push_back(a[i]);
std::sort(vc[x].begin(),vc[x].end());
}
void modify(int l,int r,int c) {
for(int i=l;i<=std::min(r,belong[l]*block);i++)
a[i]+=c;
update(belong[l]);
if(belong[l]!=belong[r]) {
for(int i=(belong[r]-1)*block+1;i<=r;i++)
a[i]+=c;
update(belong[r]);
}
for(int i=belong[l]+1;i<belong[r];i++)
delta[i]+=c;
}
int query(int l,int r,int c) {
int ans=0;
for(int i=l;i<=std::min(r,belong[l]*block);i++)
if(a[i]+delta[belong[l]]>=c) ++ans;
if(belong[l]!=belong[r])
for(int i=(belong[r]-1)*block+1;i<=r;i++)
if(a[i]+delta[belong[r]]>=c) ++ans;
for(int i=belong[l]+1;i<belong[r];i++)
ans+=block-(std::lower_bound(vc[i].begin(),vc[i].end(),c-delta[i])-vc[i].begin());
return ans;
}
public:
int work() {
scanf("%d%d",&n,&q);
block=sqrt((n+2)/3);
for(int i=1;i<=n;i++) {
scanf("%d",a+i);
belong[i]=(i-1)/block+1;
vc[belong[i]].push_back(a[i]);
if(i%block==1) ++num;
}
for(int i=1;i<=num;i++) std::sort(vc[i].begin(),vc[i].end());
while(q--) {
char opt=getchar();
while(opt!='A' && opt!='M') opt=getchar();
int lf,rg,c;scanf("%d%d%d",&lf,&rg,&c);
if(opt=='A') printf("%d\n",query(lf,rg,c));
else modify(lf,rg,c);
}
return 0;
}
}T;
int main() {return T.work();}
例题(二):弹飞绵羊
其实,只要你没学过CT,这道题还是很有希望做出来的。
记录两个信息:
\(tim[i]\) 和 \(whe[i]\) 表示:需要跳几次才能出这个块,出了这个块会到哪个点上。
每一次修改弹力系数,最多只会影响本块内会跳到这个点上的弹簧。
所以每一次修改就重构块。
于是就欢乐的解决了这道题。
(至今还没写对LCT的我就靠这个安慰自己)
#include<bits/stdc++.h>
const int maxn=2e5+5;
class Divid_Block {
private:
int a[maxn];
int belong[maxn],whe[maxn],tim[maxn];
int block,n,m;
inline int read() {
int x;char ch;while(!isdigit(ch=getchar()));
for(x=ch-'0';isdigit(ch=getchar());x=x*10+ch-'0');
return x;
}
void build() {
block=sqrt((n+2)/3);
for(int i=1;i<=n;i++)
belong[i]=(i-1)/block+1;
belong[n+1]=belong[n]+1;
for(int i=n;i;i--) {
whe[i]=i+a[i];
int r=block*belong[i]>n?n:block*belong[i];
if(whe[i]>r) tim[i]=1;
else tim[i]=tim[whe[i]]+1,whe[i]=whe[whe[i]];
}
return ;
}
void modify(int pos,int val) {
a[pos]=val;
for(int i=block*belong[pos];i>=block*(belong[pos]-1);i--) {
whe[i]=i+a[i];
if(whe[i]>block*belong[pos]) tim[i]=1;
else tim[i]=tim[whe[i]]+1,whe[i]=whe[whe[i]];
}
}
int query(int pos) {
int ans=0;
while(pos<=n) ans+=tim[pos],pos=whe[pos];
return ans;
}
public:
int work() {
n=read();
for(int i=1;i<=n;i++) a[i]=read();
build();
m=read();
while(m--) {
int opt=read(),pos=read();
++pos;
if(opt-1) {
int k=read();
modify(pos,k);
}
else printf("%d\n",query(pos));
}
return 0;
}
}T;
int main() {return T.work();}
注意事项
关于块的大小,可以参见初中dalao的博客(我的分块他教的)
还有他写的那个上了洛咕日报的博客:
lhy %%% 这个 \((A+C)/2\) 在我们机房里天天吊虐我。
分块适用范围很广,基本仅次于 \(n^{2}\) 暴力。
走投无路时可以考虑哦。