听闻分块大名已久,但仔细深究却是一个比较简单的思想~
大体思想:
分块就是把一段数分成几块来做
分块的操作一般是维护和查询,一个对于 [ l , r ] [l,r] [l,r]的操作,对于在该区间内的完整的块,我们直接维护整个块的信息;对于两侧不完整的部分,我们暴力修改每个点的信息。写分块的关键就是想好如何维护和查询。
一般情况下,当 S = N S=\sqrt{N} S=N时,时空复杂度最优。
解决问题:
分块可以处理很多区间问题,虽然复杂度要比树状数组和线段树要高,但是代码比较简短、思想直观形象。
具体实现
分块分块,首先自然需要分:
void build()
{
dis=sqrt(n); //每个块的长度
num=n/dis; //共有多少个块
for (int i=1;i<=num;i++) //给每一个块标记左右范围
{
l[i]=dis*(i-1)+1;
r[i]=dis*i;
}
r[num]=n; //最后一个r dis*i有可能大于n故要改回n
for (int i=1;i<=num;i++)
{
int ans=0;
for (int j=l[i];j<=r[i];j++)
{
ans=max(ans,a[j]); //具体题目具体分析,此题中要求维护区间最大值故记录区间最大值
block[j]=i; //记录没一个点属于哪一个块
}
maxx[i]=ans;
}
}
}
相信这个是非常好理解的,接下来我们来看几个例题:
例题:
Problem1:HDU1754 I HATE IT
Problem Description
很多学校流行一种比较的习惯。老师们很喜欢询问,从某某到某某当中,分数最高的是多少。
这让很多学生很反感。
不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要更新某位同学的成绩。
Input
本题目包含多组测试,请处理到文件结束。
在每个测试的第一行,有两个正整数
N
N
N 和
M
M
M
(
0
<
N
<
=
200000
,
0
<
M
<
5000
)
( 0<N<=200000,0<M<5000 )
(0<N<=200000,0<M<5000),分别代表学生的数目和操作的数目。
学生ID编号分别从
1
1
1编到
N
N
N。
第二行包含
N
N
N个整数,代表这
N
N
N个学生的初始成绩,其中第
i
i
i个数代表ID为
i
i
i的学生的成绩。
接下来有M行。每一行有一个字符 C (只取’Q’或’U’) ,和两个正整数A,B。
当C为’Q’的时候,表示这是一条询问操作,它询问ID从A到B(包括A,B)的学生当中,成绩最高的是多少。
当C为’U’的时候,表示这是一条更新操作,要求把ID为A的学生的成绩更改为B。
Output
对于每一次询问操作,在一行里面输出最高成绩。
题意总括:
维护一个序列支持查询最大值和单点修改
思路:
这题看起来是一个十分简单的线段树模板,不过既然我们在讲分块,就用分块来实现。
分块的思路是维护块中最大值,求区间最大值就是求区间内整块的最大值和左右两边零散的元素的最大值。
于是我们就有了代码:
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=1000005;
int n,m,dis,num;
//dis表示每个块的长度,num表示块的数量
int a[maxn],l[maxn],r[maxn],maxx[maxn],block[maxn];
//a记录原数组,l记录每个块的最左边的位置,r记录每个块最右边的位置,maxx维护每个块的最大值,block表示每个点属于哪一个块
inline int read()
{
int s=0,f=1;
char c=getchar();
while (c<'0'||c>'9')
{
if (c=='-')
{
f=-1;
}
c=getchar();
}
while (c>='0'&&c<='9')
{
s=s*10+c-48;
c=getchar();
}
return s*f;
}
void build()
{
dis=sqrt(n); //每个块的长度
num=n/dis; //共有多少个块
for (int i=1;i<=num;i++) //给每一个块标记左右范围
{
l[i]=dis*(i-1)+1;
r[i]=dis*i;
}
r[num]=n; //最后一个r dis*i有可能大于n故要改回n
for (int i=1;i<=num;i++)
{
int ans=0;
for (int j=l[i];j<=r[i];j++)
{
ans=max(ans,a[j]); //具体题目具体分析,此题中要求维护区间最大值故记录区间最大值
block[j]=i; //记录没一个点属于哪一个块
}
maxx[i]=ans;
}
}
int query(int s,int t)
{
int ans=0;
if (block[s]==block[t]) //如果块是完整的(完全包含),其实和线段树挺像的
{
for (int i=s;i<=t;i++)
{
ans=max(ans,a[i]);
}
}
else //如果块不完整,则对缺失部分进行遍历
{
for (int i=s;i<=r[block[s]];i++)
{
ans=max(ans,a[i]);
}
for (int i=block[s]+1;i<block[t];i++)
{
ans=max(ans,maxx[i]);
}
for (int i=l[block[t]];i<=t;i++)
{
ans=max(ans,a[i]);
}
}
return ans;
}
void update(int x,int y)
{
maxx[block[x]]=max(maxx[block[x]],y);
a[x]=y; //更新的时候维护最大值数组要更新且原位置也要更新
}
int main()
{
while (~scanf("%d%d",&n,&m))
{
for (int i=1;i<=n;i++)
{
a[i]=read();
}
build();
while (m--)
{
char c;
cin>>c;
int x=read(),y=read();
if (c=='Q')
{
printf("%d\n",query(x,y));
}
else
{
update(x,y);
}
}
}
return 0;
}
接下来我们来看一下loj上的9个基础的分块题: