~~推荐播客~~
「分块」数列分块入门1 – 9 by hzwer
浅谈基础根号算法——分块
博主蒟蒻,有缘人可直接观摩以上大佬的博客。。。
#6277. 数列分块入门 1
题目大意:
给出一个长为 $n$ 的数列,以及 $n$ 个操作,操作涉及区间加法,单点查值。
分块入门,区间修改+单点查询
所谓分块,实际上是一种优美的暴力,算是一种数据结构吧。。。
将区间分成许多大小相同的块,对于多出来的部分暴力去做,一般块的大小为$√n$
看了大佬们的代码,有点儿懵逼=_=
来模拟一下流程吧,可能会清晰一点儿
如图所示,暴力地将一段长度为$10$的序列分成了$4$块,其中$3$块是大小为$√n$的块。
每个数所属块的标号显然是$bl[i]=(i-1)/blo+1$,其中$blo$为块的大小(手推一下即可)
假如你暴力修改某个区间,假设为$[2,7]$,(算了,自己暴力模拟吧)
你首先要做的是暴力修改小区间,此时小区间为$[2.3]$和$[7,7]$
这里小区间一般有两个,即最左边不在整个块中的数,和最右边不在整个块中的数
手推一下,暴力修改的左边区间为$[l,min(bl[l]*blo],r)$
emmmmm,我知道暴力修改左边区间的右端点就是$bl[l]*blo]$,为什么还要和给定修改的区间右端点去$min$呢?
。。。思考一下?(大佬说显然嘛)
有可能这个待修改区间$[l,r]$就在一个块中。
暴力修改的右区间左短点为$(bl[r]-1)*blo+1$
在这之前要特判一下$bl[l]==bl[r]$,也是防止待修改区间$[l,r]$在一个块中的情况
最后,当然是对完整的块的修改,范围是$bl[l+1],bl[r]-1$,这时修改的是区间加法标记
#include<bits/stdc++.h>
#define N 1010100
using namespace std;
int n,a[N],blo[N],atag[N];
void update(int l,int r,int c){
for(int i=l;i<=min(blo[l]*blo[0],r);i++)
a[i]+=c;
if(blo[l]!=blo[r])
for(int i=(blo[r]-1)*blo[0]+1;i<=r;i++)
a[i]+=c;
for(int i=blo[l]+1;i<=blo[r]-1;i++)
atag[i]+=c;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
blo[0]=sqrt(n);
for(int i=1;i<=n;i++) blo[i]=(i-1)/blo[0]+1;//每一个数所在的块
for(int i=1;i<=n;i++){
int opt,l,r,c;
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(!opt) update(l,r,c);
else printf("%d\n",a[r]+atag[blo[r]]);
}return 0;
}
#6278. 数列分块入门 2
给出一个长为 $n$的数列,以及 $n$ 个操作,操作涉及区间加法,询问区间内小于某个值 $x$ 的元素个数。
跟随大佬的思路,既然一个题你要考虑用分块去做,那么你要考虑以下$3$项
1.不完整的块如何处理?
2.整块如何处理?
3.预处理什么信息?
此题关键在于查找区间内小于$x$的元素个数,倘若这个区间不是有序的,难道要暴力查询吗?(暴力只能针对于不完整的块)
那么就必须让他完整的块变得有序,然后二分查找这个快里$<x$的数的个数即可
修改完成之后,边上的那两个块不就部分改变了吗,怎么维护呢?
当然还是暴力,暴力清除块,在暴力添加回去。
$vector$大法好
#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<algorithm>
#define N 500056
using namespace std;
int bl[N],n,v[N],atag[N],blo;
vector<int>V[N];
void update(int x){//第x个块
V[x].clear();
for(int i=(x-1)*blo+1;i<=min(x*blo,n);i++)
V[x].push_back(v[i]);
sort(V[x].begin(),V[x].end());
}
void add(int l,int r,int val){
for(int i=l;i<=min(bl[l]*blo,r);i++)
v[i]+=val;
update(bl[l]);
if(bl[l]!=bl[r]){
for(int i=(bl[r]-1)*blo+1;i<=r;i++)
v[i]+=val;
update(bl[r]);
}
for(int i=bl[l]+1;i<=bl[r]-1;i++)
atag[i]+=val;
}
int query(int l,int r,int c){
int ans=0;
for(int i=l;i<=min(bl[l]*blo,r);i++)
if(v[i]+atag[bl[l]]<c) ++ans;
if(bl[l]!=bl[r]){
for(int i=(bl[r]-1)*blo+1;i<=r;i++)
if(v[i]+atag[bl[r]]<c) ++ans;
}
for(int i=bl[l]+1;i<=bl[r]-1;i++){
int x=c-atag[i];
ans+=lower_bound(V[i].begin(),V[i].end(),x)-V[i].begin();
}
return ans;
}
int main()
{
scanf("%d",&n);
blo=sqrt(n);
for(int i=1;i<=n;i++){
bl[i]=(i-1)/blo+1;//是除以这个块的大小
scanf("%d",&v[i]);
V[bl[i]].push_back(v[i]);
}
for(int i=1;i<=bl[n];i++)
sort(V[i].begin(),V[i].end());
for(int opt,l,r,c,i=1;i<=n;i++){
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(!opt) add(l,r,c);
else printf("%d\n",query(l,r,c*c));
}
return 0;
}
#6279. 数列分块入门 3
我只能说这些骚操作我从来没见过,仿佛打开了异世界的大门,(大佬们早就进去看了不知多少遍了)
题目大意:
给出一个长为 $n$的数列,以及 $n$ 个操作,操作涉及区间加法,询问区间内小于某个值 $x$ 的前驱(比其小的最大元素)。
上一道题是$vector$二分查找,反之就是$STL$,而这一道也是这样的,不过用的是$set$
hzwer(他想真实表达的东西):
可以在块内维护其它结构使其更具有拓展性,比如放一个 $set$ ,这样如果还有插入、删除元素的操作,会更加的方便。
即分块套其他数据结构,%%%%强无敌呀!!
分块的调试检测技巧:
可以生成一些大数据,然后用两份分块大小不同的代码来对拍,还可以根据运行时间尝试调整分块大小,减小常数。——hzwer
#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<set>
#include<algorithm>
#define N 500056
using namespace std;
int bl[N],n,v[N],atag[N],blo;
set<int>S[N];
void add(int l,int r,int val){
for(int i=l;i<=min(bl[l]*blo,r);i++){
S[bl[l]].erase(v[i]);
v[i]+=val;
S[bl[l]].insert(v[i]);
}
if(bl[l]!=bl[r])
for(int i=(bl[r]-1)*blo+1;i<=r;i++){
S[bl[r]].erase(v[i]);
v[i]+=val;
S[bl[r]].insert(v[i]);
}
for(int i=bl[l]+1;i<=bl[r]-1;i++)
atag[i]+=val;
}
int query(int l,int r,int val){
int ans=-1;
for(int i=l;i<=min(bl[l]*blo,r);i++)
if(v[i]+atag[bl[l]]<val) ans=max(ans,v[i]+atag[bl[l]]);
if(bl[l]!=bl[r]){
for(int i=(bl[r]-1)*blo+1;i<=r;i++)
if(v[i]+atag[bl[r]]<val) ans=max(ans,v[i]+atag[bl[r]]);
}
for(int i=bl[l]+1;i<=bl[r]-1;i++){
int x=val-atag[i];
set<int>::iterator it=S[i].lower_bound(x);//二分查找
if(it==S[i].begin()) continue;
--it;
ans=max(ans,*it+atag[i]);
}
return ans;
}
int main()
{
scanf("%d",&n);
blo=sqrt(n);
for(int i=1;i<=n;i++){
bl[i]=(i-1)/blo+1;//是除以这个块的大小
scanf("%d",&v[i]);
S[bl[i]].insert(v[i]);
}
for(int opt,l,r,c,i=1;i<=n;i++){
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(!opt) add(l,r,c);
else printf("%d\n",query(l,r,c));
}
return 0;
}
#6280. 数列分块入门 4
给出一个长为 $n$ 的数列,以及 $n$ 个操作,操作涉及区间加法,区间求和。
现在看着道题,你会说,这不就是线段树裸题吗,水水水水!
当你做了分块前$3$道题目后,看到这道题,你也会说,这不是分块裸题吗,随便做。
%%%%%(为什么我都说不出来呢?)
交了5遍才A掉它,太菜啦,太菜啦。。。
有两个细节,放在代码里了。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<set>
#include<algorithm>
#define N 500056
#define LL long long
using namespace std;
LL bl[N],n,v[N],atag[N],blo,sum[N];
void add(LL l,LL r,LL val){
for(LL i=l;i<=min(bl[l]*blo,r);i++)
v[i]+=val,sum[bl[l]]+=val;//修改不完整的块时那个块的区间总和也随之改变
if(bl[l]!=bl[r])
for(LL i=(bl[r]-1)*blo+1;i<=r;i++)
v[i]+=val,sum[bl[r]]+=val;
for(LL i=bl[l]+1;i<=bl[r]-1;i++)
atag[i]+=val;
}
LL query(LL l,LL r,LL mod){
LL ans=0;
for(LL i=l;i<=min(bl[l]*blo,r);i++)
ans=(ans%mod+(v[i]+atag[bl[l]])%mod)%mod;
if(bl[l]!=bl[r])
for(LL i=(bl[r]-1)*blo+1;i<=r;i++)
ans=(ans%mod+(v[i]+atag[bl[r]])%mod)%mod;
for(LL i=bl[l]+1;i<=bl[r]-1;i++)
ans=(ans%mod+(sum[i]+atag[i]*blo)%mod)%mod;//标记不要误写成bl[i]
return ans%mod;
}
int main()
{
scanf("%lld",&n);
blo=sqrt(n);
for(LL i=1;i<=n;i++){
bl[i]=(i-1)/blo+1;//是除以这个块的大小
scanf("%lld",&v[i]);
sum[bl[i]]+=v[i];
}
for(LL opt,l,r,c,i=1;i<=n;i++){
scanf("%lld%lld%lld%lld",&opt,&l,&r,&c);
if(!opt) add(l,r,c);
else printf("%lld\n",query(l,r,c+1));
}
return 0;
}
#6281. 数列分块入门 5
给出一个长为 $n$ 的数列 $a_1\ldots a_n$ ,以及 $n$ 个操作,操作涉及区间开方,区间求和。
区间开方?操作很骚啊。。。(就是不会)
不难发现,这题的修改就只有下取整开方,而一个数经过几次开方之后,它的值就会变成 0 或者 1。——hzwer
然后就可以随便搞啦。。。。(详情见代码)
#include<bits/stdc++.h>
#define N 5000000
using namespace std;
int n,bl[N],v[N],sum[N],blo;
bool flg[N];
void change_x(int x){
if(flg[x]) return;
flg[x]=1;
sum[x]=0;
for(int i=(x-1)*blo+1;i<=x*blo;i++){
v[i]=sqrt(v[i]);
if(v[i]>1) flg[x]=0;
sum[x]+=v[i];
}
}
void change(int l,int r){
for(int i=l;i<=min(bl[l]*blo,r);i++)
sum[bl[l]]-=v[i],v[i]=sqrt(v[i]),sum[bl[l]]+=v[i];
if(bl[l]!=bl[r]){
for(int i=(bl[r]-1)*blo+1;i<=min(r,n);i++){
sum[bl[r]]-=v[i],v[i]=sqrt(v[i]),sum[bl[r]]+=v[i];
}
}
for(int i=bl[l]+1;i<=bl[r]-1;i++)
change_x(i);
}
int query(int l,int r){
int ans=0;
for(int i=l;i<=min(bl[l]*blo,r);i++)
ans+=v[i];
if(bl[l]!=bl[r]){
for(int i=(bl[r]-1)*blo+1;i<=r;i++)
ans+=v[i];
}
for(int i=bl[l]+1;i<=bl[r]-1;i++)
ans+=sum[i];
return ans;
}
int main()
{
scanf("%d",&n);
blo=sqrt(n);
for(int i=1;i<=n;i++){
scanf("%d",&v[i]);
bl[i]=(i-1)/blo+1;
sum[bl[i]]+=v[i];
}
for(int opt,l,r,c,i=1;i<=n;i++){
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(!opt) change(l,r);
else printf("%d\n",query(l,r));
}
return 0;
}
P4145 上帝造题的七分钟2 / 花神游历各国
有点儿坑的地方是$l$有可能大于$r$,所以要$swap(l,r)$
#include<bits/stdc++.h>
#define LL long long
#define N 1000000
using namespace std;
LL n,m,v[N],sum[N],bl[N],blo;
bool flg[N];
void change_sqrt(LL x) {
if(flg[x]) return;
flg[x]=1;
sum[x]=0;
for(LL i=(x-1)*blo+1; i<=x*blo; i++) {
v[i]=sqrt(v[i]),sum[x]+=v[i];
if(v[i]>1) flg[x]=0;
}
}
void add(LL l, LL r) {
if(l>r) swap(l,r);
for(LL i=l; i<=min(bl[l]*blo,r); i++)
sum[bl[l]]-=v[i],v[i]=sqrt(v[i]),sum[bl[l]]+=v[i];
if(bl[l]!=bl[r]) {
for(LL i=(bl[r]-1)*blo+1; i<=r; i++) {
sum[bl[r]]-=v[i],v[i]=sqrt(v[i]),sum[bl[r]]+=v[i];
}
}
for(LL i=bl[l]+1; i<=bl[r]-1; i++)
change_sqrt(i);
}
inline LL query(LL l,LL r) {
if(l>r) swap(l,r);
LL ans=0;
for(LL i=l; i<=min(bl[l]*blo,r); i++)
ans+=v[i];
if(bl[l]!=bl[r]) {
for(LL i=(bl[r]-1)*blo+1; i<=r; i++)
ans+=v[i];
}
for(LL i=bl[l]+1; i<=bl[r]-1; i++)
ans+=sum[i];
return ans;
}
int main() {
scanf("%lld",&n);
blo=sqrt(n);
for(LL i=1; i<=n; i++) {
scanf("%lld",&v[i]);
bl[i]=(i-1)/blo+1;
sum[bl[i]]+=v[i];
}
scanf("%lld",&m);
for(LL opt,l,r,i=1; i<=m; i++) {
scanf("%lld%lld%lld",&opt,&l,&r);
if(!opt) add(l,r);
else printf("%lld\n",query(l,r));
}
return 0;
}
#6282. 数列分块入门 6
给出一个长为 $n$ 的数列,以及 $n$ 个操作,操作涉及单点插入,单点询问,数据随机生成。
暴力插入,块重构,当元一个块内素数量很多时,重新构造
$vector \&\& pair$
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>
#define N 200005
using namespace std;
int n,v[N],st[N],blo,m;
vector<int>ve[1005];
pair<int,int>query(int x){
int t=1;
while(ve[t].size()<x){
x-=ve[t].size();++t;
}
return make_pair(t,x-1);
}
void rebuild(){
int top=0;
for(int i=1;i<=m;i++){
for(vector<int>::iterator j=ve[i].begin();j!=ve[i].end();j++)
st[++top]=*j;
ve[i].clear();
}
int blo2=sqrt(top);
for(int i=1;i<=top;i++)
ve[(i-1)/blo2+1].push_back(st[i]);
m=(top-1)/blo2+1;
}
void insert(int a,int b){
pair<int,int> t=query(a);
ve[t.first].insert(ve[t.first].begin()+t.second,b);
if(ve[t.first].size()>20*blo)
rebuild();
}
int main()
{
scanf("%d",&n);blo=sqrt(n);
for(int i=1;i<=n;i++){
scanf("%d",&v[i]);
ve[(i-1)/blo+1].push_back(v[i]);
}
m=(n-1)/blo+1;//统计一共有多少块
for(int opt,l,r,c,i=1;i<=n;i++){
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(!opt) insert(l,r);
else {
pair<int,int>t=query(r);
printf("%d\n",ve[t.first][t.second]);
}
}
return 0;
}
#6283. 数列分块入门 7
区间乘法+区间加法+单点查询
修改时,对边上的块直接下放标记,乘先加后
#include<bits/stdc++.h>
#define N 200005
#define mod 10007
using namespace std;
int n,v[N],mtag[N],atag[N],bl[N],blo;
void reserve(int x) {
for(int i=(x-1)*blo+1; i<=min(x*blo,n); i++)
v[i]=(v[i]*mtag[x]%mod+atag[x]%mod)%mod;
atag[x]=0,mtag[x]=1;
}
void add(int f,int l,int r,int c) {
reserve(bl[l]);
for(int i=l; i<=min(bl[l]*blo,r); i++) {
if(f) v[i]=v[i]*c%mod;
else v[i]=(v[i]+c)%mod;
}
if(bl[l]!=bl[r]) {
reserve(bl[r]);
for(int i=(bl[r]-1)*blo+1; i<=r; i++)
if(f) v[i]=v[i]*c%mod;
else v[i]=(v[i]+c)%mod;
}
for(int i=bl[l]+1; i<=bl[r]-1; i++) {
if(f) atag[i]=atag[i]*c%mod,mtag[i]=mtag[i]*c%mod;
else atag[i]=(atag[i]+c)%mod;
}
}
int main() {
scanf("%d",&n);
blo=sqrt(n);
for(int i=1; i<=n; i++) {
scanf("%d",&v[i]);
bl[i]=(i-1)/blo+1;
mtag[bl[i]]=1;
}
for(int opt,l,r,c,i=1; i<=n; i++) {
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(opt==2) printf("%d\n",(v[r]*mtag[bl[r]]%mod+atag[bl[r]]%mod)%mod);
else add(opt,l,r,c);
}
return 0;
}
P3373 【模板】线段树 2
自己的分块自带大常数,qwq,只能做$70$分,还是太弱啦。。。
// luogu-judger-enable-o2
# pragma GCC optimize "O3"
#include<bits/stdc++.h>
#define N 200005
#define LL long long
#define IL inline
#define RG register
using namespace std;
IL void in(RG LL &x){
register char c=getchar();x=0;int f=1;
while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
x*=f;
}
IL void print(RG LL x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
LL n,v[N],mtag[N],atag[N],bl[N],blo,mod,m,sum[N];
IL void reserve(RG LL x) {
for(LL i=(x-1)*blo+1; i<=min(x*blo,n); i++)
v[i]=(v[i]*mtag[x]%mod+atag[x]%mod)%mod;
atag[x]=0,mtag[x]=1;
}
IL void add(RG LL f,RG LL l,RG LL r,RG LL c) {
reserve(bl[l]);
for(RG LL i=l; i<=min(bl[l]*blo,r); i++) {
if(f==1) sum[bl[l]]+=v[i]*(c-1)%mod,v[i]=v[i]*c%mod;
else v[i]=(v[i]+c)%mod,sum[bl[l]]+=c;
sum[bl[l]]%=mod;
}
if(bl[l]!=bl[r]) {
reserve(bl[r]);
for(RG LL i=(bl[r]-1)*blo+1; i<=r; i++) {
if(f==1) sum[bl[r]]+=v[i]*(c-1)%mod,v[i]=v[i]*c%mod;
else v[i]=(v[i]+c)%mod,sum[bl[r]]+=c;
sum[bl[r]]%=mod;
}
for(RG LL i=bl[l]+1; i<=bl[r]-1; i++) {
if(f==1) atag[i]=atag[i]*c%mod,mtag[i]=mtag[i]*c%mod,sum[i]=c*sum[i]%mod;
else atag[i]=(atag[i]+c)%mod,sum[i]=(sum[i]+blo*c)%mod;
}
}
}
IL LL query(RG LL l,RG LL r) {
RG LL ans=0;
for(RG LL i=l; i<=min(bl[l]*blo,r); i++)
ans=(ans+v[i]*mtag[bl[l]]%mod+atag[bl[l]]%mod)%mod;
if(bl[l]!=bl[r]) {
for(RG LL i=(bl[r]-1)*blo+1; i<=r; i++)
ans=(ans+v[i]*mtag[bl[r]]%mod+atag[bl[r]]%mod)%mod;
}
for(RG LL i=bl[l]+1; i<=bl[r]-1; i++) ans=(ans+sum[i])%mod;
return ans;
}
int main() {
in(n),in(m),in(mod);
blo=sqrt(n);
for(RG LL i=1; i<=n; i++) {
in(v[i]);
bl[i]=(i-1)/blo+1;
mtag[i]=1;
sum[bl[i]]=(sum[bl[i]]+v[i])%mod;
}
for(RG LL opt,l,r,c,i=1; i<=m; i++) {
in(opt),in(l),in(r);
if(opt==3) print(query(l,r)%mod),putchar('\n');
else in(c),add(opt,l,r,c);
}
return 0;
}
来源:oschina
链接:https://my.oschina.net/u/4382335/blog/3867824