【算法学习笔记】分块——优雅的暴力
“这个世界本是没有分块的,小数据的题多了,便有了分块。” ——沃镃基硕德
简介
分开是一种毒瘤优秀的数据结构,它的基本思想就是对一个数列分为几个小“块”,对于查询中的整块可以直接一扫而过,剩下的七零八碎的东西直接暴力。
很显然,我们会发现分的块越小,数量就越多,维护整块的信息就变的趋近于零块。而相反,块越大,维护块内信息就变得越慢,所以我们需要找一个折中的大小。
我们设数列长度为,块的大小为,块的个数为
假设我们对全局进行处理,则时间复杂度应为。
我们考虑如何使时间开销最小,这里运用的数学知识为均值不等式:
由于,所以
基本分块
分块1
人话翻译:区间修改,单点查询。
本题是分块入门题。具体思想和线段树差不多,都是为某一个区间打上一个标记,并对整块进行标记修改,零块直接修改。和线段树不同的是分块的标记不用下放(无相交区间)。
#include<bits/stdc++.h>
using namespace std;
#define rg register
#define int long long
#define ull unsigned long long
namespace Enterprise{
inline int read(){
rg int s=0,f=0;
rg char ch=getchar();
while(not isdigit(ch)) f|=(ch=='-'),ch=getchar();
while(isdigit(ch)) s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
return f?-s:s;
}
const int N=5e4+15;
int a[N],n,tag[255],size,pos[N];
inline void main(){
n=read();
size=(int)sqrt(n);
for(rg int i=1;i<=n;i++){
pos[i]=(i-1)/size+1;
a[i]=read();
}
for(rg int i=1;i<=n;i++){
int opt=read(),l=read(),r=read(),c=read();
if(!opt){
rg int lx=pos[l],rx=pos[r];
for(rg int j=l;pos[j]==lx&&j<=r;j++) a[j]+=c;
if(lx==rx) continue;
for(rg int j=r;pos[j]==rx;j--) a[j]+=c;
for(rg int j=lx+1;j<rx;j++) tag[j]+=c;
}else{
printf("%lld\n",a[r]+tag[pos[r]]);
}
}
}
}
signed main(){
Enterprise::main();
return 0;
}
分块2
人话翻译:区间修改,统计区间严格小于某个数的数的个数。
本题要注意的是每次零块必须重新sort,而对于整块修改,由于统一加了一个相同的数,不用重新sort。
这里的代码用到了一个小技巧:利用每块下标为的位置来存储本块中所含元素的个数。
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cctype>
using namespace std;
#define rg register
#define int long long
#define ull unsigned long long
namespace Enterprise{
inline int read(){
rg int s=0,f=0;
rg char ch=getchar();
while(not isdigit(ch)) f|=(ch=='-'),ch=getchar();
while(isdigit(ch)) s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
return f?-s:s;
}
const int N=5e5+15;
int a[N],tag[500],pos[N],n,size,b[500][500],sum;
#define s(x) ((x-1)*size+1)
#define e(x) (x*size)
inline void _sort(int x){
// memset(b[x],0,sizeof(b[x]));
b[x][0]=0;
for(rg int i=s(x);i<=min(n,e(x));i++) b[x][++b[x][0]]=a[i];
sort(b[x]+1,b[x]+b[x][0]+1);
}
inline void update(int l,int r,int v){
rg int lx=pos[l],rx=pos[r];
for(rg int i=l;pos[i]==lx&&i<=r;i++) a[i]+=v;
_sort(lx);
if(lx==rx) return;
for(rg int i=r;pos[i]==rx;i--) a[i]+=v;
_sort(rx);
for(rg int i=lx+1;i<rx;i++) tag[i]+=v;
}
inline int query(int l,int r,int v){
rg int ans=0,lx=pos[l],rx=pos[r];
for(rg int i=l;pos[i]==lx&&i<=r;i++){
if(a[i]+tag[lx]<v) ans++;
}
if(lx==rx) return ans;
for(rg int i=r;pos[i]==rx;i--){
if(a[i]+tag[rx]<v) ans++;
}
for(rg int i=lx+1;i<rx;i++){
rg int tmp=0,ll=1,rr=b[i][0];
while(ll<=rr){
rg int mid=(ll+rr)>>1;
if(b[i][mid]+tag[i]>=v) rr=mid-1;
else ll=mid+1,tmp=mid;
}
ans+=tmp;
}
return ans;
}
inline void main(){
n=read();
size=(int)sqrt(n);
sum=(n/size)+(n%size>0);
for(rg int i=1;i<=n;i++){
// a[i]=read();
pos[i]=(i-1)/size+1;
// id[i]=(i-1)%size+1;
b[pos[i]][++b[pos[i]][0]]=a[i]=read();
}
for(rg int i=1;i<=sum;i++) sort(b[i]+1,b[i]+b[i][0]+1);
for(rg int i=1;i<=n;i++){
rg int opt=read(),l=read(),r=read(),v=read();https://loj.ac/problem/6279
if(!opt) update(l,r,v);
else printf("%lld\n",query(l,r,v*v));
}
}
}
signed main(){
Enterprise::main();
return 0;
}
分块3
人话翻译:区间修改,区间查找前驱。
- 前驱是指严格小于某个数的数中的最大值,即查询
根据前驱定义可得此题块内二分即可完成
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cctype>
using namespace std;
#define rg register
#define int long long
#define ull unsigned long long
namespace Enterprise{
inline int read(){
rg int s=0,f=0;
rg char ch=getchar();
while(not isdigit(ch)) f|=(ch=='-'),ch=getchar();
while(isdigit(ch)) s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
return f?-s:s;
}
const int N=1e6+15;
int a[N],tag[500],pos[N],n,size,b[500][500],sum;
#define s(x) ((x-1)*size+1)
#define e(x) (x*size)
inline void _sort(int x){
// memset(b[x],0,sizeof(b[x]));
b[x][0]=0;
for(rg int i=s(x);i<=min(n,e(x));i++) b[x][++b[x][0]]=a[i];
sort(b[x]+1,b[x]+b[x][0]+1);
}
inline void update(int l,int r,int v){
rg int lx=pos[l],rx=pos[r];
for(rg int i=l;pos[i]==lx&&i<=r;i++) a[i]+=v;
_sort(lx);
if(lx==rx) return;
for(rg int i=r;pos[i]==rx;i--) a[i]+=v;
_sort(rx);
for(rg int i=lx+1;i<rx;i++) tag[i]+=v;
}
inline int query(int l,int r,int v){
rg int ans=-1,lx=pos[l],rx=pos[r];
for(rg int i=l;pos[i]==lx&&i<=r;i++){
if(a[i]+tag[lx]<v) ans=max(ans,a[i]+tag[lx]);
}
if(lx==rx) return ans;
for(rg int i=r;pos[i]==rx;i--){
if(a[i]+tag[rx]<v) ans=max(ans,a[i]+tag[rx]);
}
for(rg int i=lx+1;i<rx;i++){
rg int ll=1,rr=b[i][0];
while(ll<=rr){
rg int mid=(ll+rr)>>1;
if(b[i][mid]+tag[i]<v) ans=max(ans,b[i][mid]+tag[i]),ll=mid+1;
else rr=mid-1;
}
}
return ans;
}
inline void main(){
n=read();
size=(int)sqrt(n);
sum=(n/size)+(n%size>0);
for(rg int i=1;i<=n;i++){
// a[i]=read();
pos[i]=(i-1)/size+1;
// id[i]=(i-1)%size+1;
b[pos[i]][++b[pos[i]][0]]=a[i]=read();
}
for(rg int i=1;i<=sum;i++) sort(b[i]+1,b[i]+b[i][0]+1);
for(rg int i=1;i<=n;i++){
rg int opt=read(),l=read(),r=read(),v=read();
if(!opt) update(l,r,v);
else{
rg int out=query(l,r,v);
printf("%lld\n",out);
}
}
}
}
signed main(){
Enterprise::main();
return 0;
}
分块4
人话翻译:区间修改,区间查询
只需开一个整块存和的数组即可。
#include<bits/stdc++.h>
using namespace std;
#define rg register
#define int long long
#define ull unsigned long long
namespace Enterprise{
inline int read(){
rg int s=0,f=0;
rg char ch=getchar();
while(not isdigit(ch)) f|=(ch=='-'),ch=getchar();
while(isdigit(ch)) s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
return f?-s:s;
}
const int N=5e4+15;
int a[N],pos[N],tag[500],sum[500],n,size;
inline void update(int l,int r,int v){
rg int lx=pos[l],rx=pos[r];
for(rg int i=l;pos[i]==lx&&i<=r;i++){
a[i]+=v;
sum[lx]+=v;
}
if(lx==rx) return;
for(rg int i=r;pos[i]==rx;i--){
a[i]+=v;
sum[rx]+=v;
}
for(rg int i=lx+1;i<rx;i++){
sum[i]+=v*size;
tag[i]+=v;
}
}
inline int query(int l,int r){
rg int lx=pos[l],rx=pos[r];
rg int ans=0;
for(rg int i=l;pos[i]==lx&&i<=r;i++) ans+=a[i]+tag[lx];
if(lx==rx) return ans;
for(rg int i=r;pos[i]==rx;i--) ans+=a[i]+tag[rx];
for(rg int i=lx+1;i<rx;i++) ans+=sum[i];
return ans;
}
inline void main(){
n=read();
size=sqrt(n);
for(rg int i=1;i<=n;i++){
pos[i]=(i-1)/size+1;
sum[pos[i]]+=a[i]=read();
}
for(rg int i=1;i<=n;i++){
rg int opt=read(),l=read(),r=read(),v=read();
if(!opt) update(l,r,v);
else printf("%lld\n",query(l,r)%(v+1));
}
}
}
signed main(){
Enterprise::main();
return 0;
}
分块5
在此链接三倍经验题与其题解
由于本题数据范围为,每个数 所以可知,最多开4次就可以变为1。很显然,对1进行开平方是没有意义的。所以我们只需对整块加入一个最大标记。
线段树明显可过此题,所以我就用线段树过了此题
本文作者:
本文链接:https://www.cnblogs.com/UssEnterprise/p/12088705.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步