分块是一种思想,把一个整体划分为若干个小块,对整块整体处理,零散块单独处理。本文主要介绍块状数组——利用分块思想处理区间问题的一种数据结构。
块状数组把一个长度为 n n n 的数组划分为 a a a 块,每块长度为 n / a n/a n/a 。对于一次区间操作,对区间內部的整块进行整体的操作,对区间边缘的零散块单独暴力处理。(所以分块被称为“优雅的暴力”)
这里,块数既不能太少也不能太多。如果太少,区间中整块的数量会很少,我们要花费大量时间处理零散块;如果太多,又会让块的长度太短,失去整体处理的意义。一般来说,我们取块数为$ \sqrt[]{{n}}$ ,这样在最坏情况下,我们要处理接近 n \sqrt[]{{n}} n个整块,还要对长度为 2 ∗ n 2*\sqrt[]{{n}} 2∗n的零散块单独处理,总时间复杂度为 n \sqrt[]{{n}} n 。这是一种根号算法。但实际上,有时候我们要根据数据范围、具体复杂度来确定。
显然,分块的时间复杂度比不上线段树和树状数组这些对数级算法。但由此换来的,是更高的灵活性。与线段树不同,块状数组并不要求所维护信息满足结合律,也不需要一层层地传递标记。
常用的预处理模板:
void init()
{
int sq = sqrt( n );//开根
for(int i=1;i<=n;i++)
{
bl[i] = (i-1)/sq + 1; //给每个位置i确定所属的块的编号
}
for(int i=1;i<=bl[n];i++)
{
st[i] = (i-1)*sq + 1;//第i块的开始位置
ed[i] = i*sq;//第i块的终止位置
}
ed[ bl[n] ] = n;//注意最后一块特别处理
}
T1
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define LL long long
#define N 101000
#define INF 0x3f3f3f3f
using namespace std;
LL n,m,a[N],bel[N],sum[N],lazy[N],st[1000],ed[1000],sq,size[1000],ans;
void init()
{
sq = sqrt(n);
for(LL i=1;i<=sq;i++)
{
st[i] = n / sq * (i-1) + 1;
ed[i] = n / sq * i;
}
ed[sq] = n;
for(LL i=1;i<=sq;i++)
{
for(LL j=st[i];j<=ed[i];j++)
{
bel[j] = i;
}
}
}
void solve_fenkuai()
{
cin>>n;
for(LL i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
init();
for(int q=1;q<=n;q++)
{
LL x,y,k;
LL op;
scanf("%lld",&op);
if( op==0 )
{
scanf("%lld %lld %lld",&x,&y,&k);
if( bel[x] == bel[y] )
{
for(int i=x;i<=y;i++)
{
a[i] += k;
}
}
else
{
for(LL i=x;i<=ed[ bel[x] ];i++)
{
a[i] += k;
}
for(LL i=st[ bel[y] ];i<=y;i++)
{
a[i] += k;
}
for(LL i=bel[x]+1;i<=bel[y]-1;i++)
{
lazy[i] += k;
}
}
}
else
{
scanf("%lld %lld %lld",&x,&y,&k);
printf("%lld\n",a[y] + lazy[ bel[y] ]);
}
}
}
int main()
{
//freopen("in.txt","r",stdin);
solve_fenkuai();
return 0;
}
[
T2
#include<bits/stdc++.h>
#define LL long long
#define N 1000001
#define SQ 2010
using namespace std;
LL a[N],size[SQ],st[SQ],ed[SQ],lazy[SQ],bel[N],n,m,sq,l,r,w,c,ans,x,y;
vector<LL> v[SQ];
void init_block()
{
sq = sqrt( n );
for(int i=1;i<=sq;i++)
{
st[i] = n/sq * (i-1) + 1;
ed[i] = n/sq * i;
}
ed[sq] = n;
for(int i=1;i<=sq;i++)
{
for(int j=st[i];j<=ed[i];j++)
{
bel[j] = i;
}
size[i] = ed[i] - st[i] + 1;
}
}
void update(int p)
{
v[p].clear();
for(int i=0;i<=size[i];i++)//
{
v[p].push_back( a[ st[p] + i ] );
}
sort( v[p].begin() , v[p].end() );
}
void solve()
{
cin>>n;
// getchar();
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
init_block();
for(int i=1;i<=sq;i++)
{
for(int j=st[i];j<=ed[i];j++)
{
v[i].push_back( a[j] );
}
sort( v[i].begin() , v[i].end() );
}
while( n-- )
{
LL op;
scanf("%lld",&op);
if( op==0 )
{
scanf("%lld %lld %lld",&x,&y,&w);
if( bel[x]==bel[y] )
{
for(int i=x;i<=y;i++)
{
a[i] += w;
}
update( bel[x] );
continue;
}
for(int i=x;i<=ed[ bel[x] ];i++)
{
a[i] += w;
}
update( bel[x] );
for(int i=st[ bel[y] ];i<=y;i++)
{
a[i] += w;
}
update( bel[y] );
for(int i=bel[x]+1;i<=bel[y]-1;i++)
{
lazy[i] += w;
}
}
else
{
scanf("%lld %lld %lld",&x,&y,&c);
c *= c;
ans = 0;
if( bel[x] == bel[y] )
{
for(int i=x;i<=y;i++)
{
if( a[i]+lazy[ bel[x] ] < c ) ans++;
}
printf("%lld\n",ans);
continue;
}
for(int i=x;i<=ed[ bel[x] ];i++)
{
if( a[i]+lazy[ bel[x] ] < c ) ans++;
}
for(int i=st[ bel[y] ];i<=y;i++)
{
if( a[i]+lazy[ bel[y] ] < c ) ans++;
}
for(int i=bel[x]+1;i<=bel[y]-1;i++)
{
ans += size[i] - (v[i].end() - lower_bound( v[i].begin(), v[i].end(), c-lazy[i] ));
}
printf("%lld\n",ans);
}
}
}
int main()
{
//freopen("in.txt","r",stdin);
solve();
return 0;
}
T7
很显然,如果只有区间乘法,和分块入门 1 的做法没有本质区别,但要思考如何同时维护两种标记。
我们让乘法标记的优先级高于加法(如果反过来的话,新的加法标记无法处理)
若当前的一个块乘以 m t a g 1 mtag1 mtag1(乘法标记)后加上 a t a g 1 atag1 atag1(加法标记),这时进行一个乘 m t a g 2 mtag2 mtag2的操作,则原来的标记变成 m t a g 1 ∗ m t a g 2 mtag1*mtag2 mtag1∗mtag2, a t a g 1 atag1 atag1* m t a g 2 mtag2 mtag2
若当前的一个块乘以
m
t
a
g
mtag
mtag后加上a1,这时进行一个加a2的操作,则原来的标记变成
m
t
a
g
mtag
mtag,
a
t
a
g
1
+
a
t
a
g
2
atag1+atag2
atag1+atag2
ACcode:
#include<bits/stdc++.h>
#define LL long long
#define N 110000
#define SQ 10100
using namespace std;
const int MOD = 10007;
int sq,n,t,a[N],mtag[SQ],atag[SQ],bl[N];
void reset(int id)//把标记放下放
{
for(int i = (id-1)*sq+1;i<=min( id*sq , n );i++ )
{
a[i] = (a[i]*mtag[ id ] + atag[ id ]) % MOD;
}
atag[id] = 0;
mtag[id] = 1;
}
void solve(int op,int l,int r,int c)
{
reset( bl[l] );
for(int i=l;i<=min( bl[l]*sq , r );i++)
{
if( op==0 ) a[i] += c;
else a[i] *= c;
a[i] %= MOD;
}
if( bl[l] != bl[r] )
{
reset( bl[r] );
for(int i=(bl[r]-1)*sq+1;i<=r;i++)
{
if( op==0 ) a[i] += c;
else a[i] *= c;
a[i] %= MOD;
}
}
for(int i=bl[l]+1;i<=bl[r]-1;i++)
{
if( op==0 )
{
atag[i] = (atag[i]+c)%MOD;
}
else
{
mtag[i] = (mtag[i]*c)%MOD;
atag[i] = (atag[i]*c)%MOD;
}
}
}
int main()
{
//freopen("in.txt","r",stdin);
cin>>n;
sq = sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
bl[i] = (i-1)/sq + 1;
}
for(int i=1;i<=bl[n];i++) mtag[i] = 1;
for(int i=1;i<=n;i++)
{
int op,l,r,c;
scanf("%d %d %d %d",&op,&l,&r,&c);
if( op==2 ) printf("%d\n",(a[r]*mtag[ bl[r] ] + atag[ bl[r] ])%MOD);
else solve( op,l,r,c );
}
return 0;
}
T8
#include<bits/stdc++.h>
#define int long long
#define LL long long
#define N 100100
#define SQ 110000
using namespace std;
int n,a[N],bl[N],sq;
int pd[SQ],b[SQ];
int st[SQ],ed[SQ];
void reset(int id)//下放标记
{
if( b[id]==-1 ) return ;
int rr = min( id*sq , n );
for(int i=(id-1)*sq+1;i<=rr;i++
{
a[i] = b[id];
}
b[id] = -1;
}
int find1(int l,int r,int c)
{
int sum = 0;
reset( bl[l] );
for(int i=l;i<=min( bl[l]*sq ,r );i++)
{
if( a[i]==c ) sum++;
else a[i] = c;
}
if( bl[r]==bl[l] ) return sum;
reset( bl[r] );
for(int i=( bl[r]-1 )*sq + 1 ;i<=r;i++)
{
if( a[i]==c ) sum++;
else a[i] = c;
}
for(int i=bl[l]+1;i<=bl[r]-1;i++)
{
if( b[i]!=-1 )
{
if( b[i]==c ) sum += sq;
else b[i] = c;
}
else
{
int rr = min( i*sq , n );
for(int j=(i-1)*sq+1;j<=rr;j++)
{
if( a[j]==c ) sum++;
a[j] = c;
}
b[i] = c;
}
}
return sum;
}
void solve()
{
memset(b,-1,sizeof(b));
scanf("%lld",&n);
sq = sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
bl[i] = (i-1)/sq + 1;
}
// init();
for(int i=1;i<=sq;i++)
{
st[i] = (i-1)*sq+1;
ed[i] = i*sq;
}
// ed[sq] = n;
for(int i=1;i<=n;i++)
{
int c,l,r;
scanf("%lld %lld %lld",&l,&r,&c);
printf("%lld\n", find1( l,r,c ) );
}
}
signed main()
{
//freopen("in.txt","r",stdin);
solve();
return 0;
}
T9
这题很有意思,我们要先发现一个性质,对于区间 [ l , r ] [l,r] [l,r]的众数一定满足下面三种情况:
- b l [ l ] bl[l] bl[l]这个块中 l l l之后的部分的众数
- b l [ r ] bl[r] bl[r]这个块中 r r r之前的部分的众数
- b l [ l ] + 1 bl[l]+1 bl[l]+1到 b l [ r ] − 1 bl[r]-1 bl[r]−1块的众数
上述第三个可以预处理出来,对于每次询问,直接枚举第一二种情况的数字即可。
#include<bits/stdc++.h>
#define LL long long
#define PB push_back
#define N 101010
#define SQ 1251
using namespace std;
map<int,int> ma;
int n,a[N],f[SQ][SQ],cnt[N],bl[N],tot = 0,val[N],sq;
int st[SQ],ed[SQ];
vector<int> ve[N];//存储编号相同时为原来的第几个值,用于二分求众数
//利用ma离散化数据,f[i][j]记录从第i块到第j块内的最小众数,val记录真实大小,a离散化后存的是编号大小,st,ed分别存了一个块的开头结尾位置
void pre(int id)//预处理f[i][j]数组
{
memset(cnt,0,sizeof(cnt));
int maxx = 0,ans = 0;
for(int i=st[id];i<=n;i++)//从当前块的开始位置一直扫扫到n
{
int v = bl[ i ];
cnt[ a[i] ]++;
if( cnt[ a[i] ] > maxx || ( cnt[ a[i] ]==maxx && val[ a[i] ] < val[ ans ] ) )
{
ans = a[i];
maxx = cnt[ a[i] ];
}
f[id][v] = ans;
}
}
int query2(int l,int r,int x)//二分求一个数字x在区间[l,r]出现的次数!!!!
{
int v = upper_bound(ve[x].begin(),ve[x].end(),r)-lower_bound(ve[x].begin(),ve[x].end(),l);
return v;
}
int query(int l,int r)
{
int ans = f[ bl[l]+1 ][ bl[r]-1 ];//中间一整块的众数
int maxx = query2(l,r,ans);
for(int i=l;i<=min( r,ed[ bl[l] ] );i++)
{
int sum = query2( l,r,a[i] );
if( sum>maxx || ( sum==maxx && val[ a[i] ]<val[ ans ] ) )
{
maxx = sum;
ans = a[i];
}
}
if( bl[l] != bl[r] )
{
for(int i=st[ bl[r] ];i<=r;i++)
{
int sum = query2(l,r,a[i]);
if( sum>maxx || ( sum==maxx && val[ a[i] ] < val[ ans ] ) )
{
ans = a[i];
maxx = sum;
}
}
}
return val[ ans ];
}
void solve()
{
scanf("%d",&n);
sq = 80;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if( !ma[a[i]] )
{
ma[ a[i] ] = ++tot;
val[tot] = a[i];
}
a[i] = ma[ a[i] ];
ve[ a[i] ].PB( i );
bl[i] = (i-1)/sq + 1;
}
for(int i=1;i<=bl[n];i++)
{
st[i] = (i-1)*sq + 1;
ed[i] = i*sq;
}
ed[bl[n]] = n;
for(int i=1;i<=bl[n];i++) pre(i);
for(int i=1;i<=n;i++)
{
int l,r;
scanf("%d %d",&l,&r);
if( l>r ) swap(l,r);
printf("%d\n",query(l,r));
}
}
int main()
{
//freopen("in.txt","r",stdin);
solve();
return 0;
}
T10 (分块)蒲公英
其实跟T9差不多…
#include<bits/stdc++.h>
#define LL long long
#define N 40100
#define SQ 500
#define PB push_back
using namespace std;
map<int,int> ma;
int st[SQ],ed[SQ],cnt[40010],bl[N],n,m,a[N],val[N],tot = 0,sq,f[501][501];
vector<int> ve[40010];
void init()
{
val[0] = -1;
for(int i=1;i<=n;i++)
{
bl[i] = (i-1)/sq + 1;
}
for(int i=1;i<=bl[n];i++)
{
st[i] = (i-1)*sq + 1;
ed[i] = i*sq;
}
ed[ bl[n] ] = n;
}
void pre(int x)
{
memset(cnt,0,sizeof(cnt));
int v, maxx = 0, ans = 0;
for(int i=st[x];i<=n;i++)
{
v = bl[i];
cnt[ a[i] ]++;
if( cnt[ a[i] ]>maxx || ( cnt[ a[i] ]==maxx && val[ a[i] ]<val[ ans ] ) )
{
maxx = cnt[ a[i] ];
ans = a[i];
}
f[x][v] = ans;
}
}
int query_cnt(int l,int r,int x)
{
return
upper_bound( ve[x].begin(), ve[x].end(), r ) - lower_bound( ve[x].begin(), ve[x].end(), l );
}
int query(int l,int r)
{
int ans= f[ bl[l]+1 ][ bl[r]-1 ],maxx = query_cnt( l,r,ans );
for(int i=l;i<=min( r,ed[ bl[l] ] );i++)
{
int v = query_cnt( l,r,a[i] );
if( v>maxx || (( v==maxx && val[ a[i] ]<val[ ans ] )) )
{
maxx = v;
ans = a[i];
}
}
if( bl[l] != bl[r] )
{
for(int i=st[ bl[r] ];i<=r;i++)
{
int v = query_cnt( l,r,a[i] );
if( v>maxx || ( v==maxx && val[ a[i] ]<val[ans] ) )
{
maxx = v;
ans = a[i];
}
}
}
return val[ans];
}
void solve()
{
cin>>n>>m;
sq = sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if( ma[ a[i] ]==0 )
{
ma[ a[i] ] = ++tot;
val[tot] = a[i];
}
a[i] = ma[ a[i] ];
ve[ a[i] ].PB( i );
}
init();
int l,r,x = 0;
for(int i=1;i<=bl[n];i++)
pre(i);
for(int i=1;i<=m;i++)
{
scanf("%d %d",&l,&r);
l = (l+x-1)%n + 1;
r = (r+x-1)%n + 1;
if( l>r ) swap( l,r );
x = query( l,r );
printf("%d\n",x);
}
}
int main()
{
//freopen("in.txt","r",stdin);
solve();
return 0;
}