题目链接:http://poj.org/problem?id=3264
知识点参考@qscqesze
很简单的模板题,用线段树维护区间最值,每次查询的时候用最大值-最小值就是答案。
为什么在标题上打了个问号(?)呢,因为线段树大家都会,我也就不班门弄斧了,(我会放上线段树的代码)。今天我想说的是一种暴力算法--分块
先说结论,在POJ上,cout比printf快了将近1000MS,分块(n*sqrt(n))比线段树(n*log(n))快了500MS,试验了好多次得出来得结论。(UPD:队友的线段树比我的快好多qwq)
不知道为什么 分块永远比线段树跑得快,欢迎大家为我解答([email protected])(QQ304960005)
言归正传,分块是一种比较优美的暴力算法,对于一个长度为n的区间,它将区间分成sqrt(n)块,很显然每一块是sqrt(n)个。
(为什么要分成sqrt(n)块呢,因为根据基本不等式(大概是基本不等式,均衡不等式?),分成sqrt(n)块的平均时间复杂度最低,当然也可以分成各种大小的块啦)
如图:
首先,sqrt(n)不一定是整数,所以区间可能被分成了sqrt(n)+1个块(大概是这样,具体我也没推算),那么这种情况下,最后一个块可能没有sqrt(n)个数(这个大家应该都能明白,我就不解释了)。
其次,对于询问区间[l,r],我们也不能保证l刚好是块的分割点,r也刚好是块的分割点,所以我们每次都要询问区间[l,A],区间[A,B]和区间[B,r],三个部分的信息整合起来就是区间[l,r]的信息,(这个看图应该很好理解)
以上就是分块的大体内容。下面具体说说分块的写法:
block表示块的大小,num表示区间中有多少块,l[maxn]和r[maxn]分别表示区间中每一块的左端点和右端点(具体怎么算的看代码推一下就理解了),belong[maxn]表示i属于哪一块。这些是分块的必须部分。
build():先用输入信息初始化一下各个块的信息,这个题目是最值,那么我们就for一遍区间[1,n],先将每一块最值保存好。
update():这个题目是个离线题,没有update
ask(l,r):询问区间[l,r],
(1)首先判断[l,r]是不是在一个块里面,如果在,那么就直接for一遍[l,r],因为是在块里面,所以复杂度是O(sqrt(n))
(2)其次如果不在一个块里面,如上图,我们就先遍历[l,A],用a[i]更新信息,然后遍历[A,B],用块里面的信息更新(Max和Min是块),然后遍历[B,r],用a[i]更新信息。(复杂度是左边+右边大概2*sqrt(n),中间num个块也是sqrt(n),加起来是3*sqrt(n)),复杂度是sqrt(n)。
下面是分块代码:
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 5e4+7;
const int INF = 1e9+7;
int belong[maxn],num,l[maxn],r[maxn],a[maxn],n,q,Max[maxn],Min[maxn],block;
void init()
{
memset(Min,0x7f,sizeof(Min));
}
void build()
{
block = sqrt(n);
num = n/block;
if(n%block)
{
num++;
}
for(int i=1;i<=num;i++)
{
l[i] = (i-1)*block+1;
r[i] = i*block;
}
r[num] = n;
for(int i=1;i<=n;i++)
{
belong[i] = (i-1)/block+1;
}
for(int i=1;i<=num;i++)
{
for(int j=l[i];j<=r[i];j++)
{
Max[i] = max(Max[i],a[j]);
Min[i] = min(Min[i],a[j]);
}
}
}
int ask(int x,int y)
{
int mx = 0;
int mn = INF;
if(belong[x]==belong[y])
{
for(int i=x;i<=y;i++)
{
mx = max(mx,a[i]);
mn = min(mn,a[i]);
}
return mx-mn;
}
for(int i=x;i<=r[belong[x]];i++)
{
mx = max(mx,a[i]);
mn = min(mn,a[i]);
}
for(int i=belong[x]+1;i
分块真的比线段树快,肯定是我的线段树假了!
这个是线段树代码:
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int INF = 1e9+7;
const int maxn = 5e4+7;
int Max[maxn*4],Min[maxn*4];
int a[maxn];
int mx = 0,mn = INF;
void init()
{
memset(a,0,sizeof(a));
memset(Max,0,sizeof(Max));
memset(Min,0x7f,sizeof(Min));
}
void pushup(int rt)
{
Max[rt] = max(Max[rt<<1],Max[rt<<1|1]);
Min[rt] = min(Min[rt<<1],Min[rt<<1|1]);
}
void build(int rt,int l,int r)
{
if(l==r)
{
Max[rt] = a[l];
Min[rt] = a[l];
return;
}
int m = (l+r)>>1;
build(rt<<1,l,m);
build(rt<<1|1,m+1,r);
pushup(rt);
}
void query(int L,int R,int l,int r,int rt)
{
if(L<=l &&R>=r)
{
mx = max(Max[rt],mx);
mn = min(Min[rt],mn);
return;
}
int m = (l+r)>>1;
if(L<=m)
{
query(L,R,l,m,rt<<1);
}
if(R>m)
{
query(L,R,m+1,r,rt<<1|1);
}
}
int main()
{
int n,q;
while(cin>>n>>q)
{
init();
for(int i=1;i<=n;i++)
{
scanf("%d",a+i);
}
build(1,1,n);
for(int i=0;i
研究了好久,不知道为什么分块总是比线段树快?!