线段树模板?VS分块【POJ3264】

题目链接: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 <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
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<belong[y];i++)
	{
		mx = max(mx,Max[i]);
		mn = min(mn,Min[i]);
	}
	for(int i=l[belong[y]];i<=y;i++)
	{
		mx = max(mx,a[i]);
		mn = min(mn,a[i]);
	}
	return mx-mn;
}
int main()
{
	while(~scanf("%d%d",&n,&q))
	{
		init();
		for(int i=1;i<=n;i++)
		{
			scanf("%d",a+i);
		}
		build();
		for(int i=0;i<q;i++)
		{
			int l,r;
			scanf("%d%d",&l,&r);
			cout<<ask(l,r)<<endl;
		}
	}
	return 0;
}

分块真的比线段树快,肯定是我的线段树假了!

这个是线段树代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
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<q;i++)
		{
			int l,r;
			scanf("%d%d",&l,&r);
			mx = 0;
			mn = INF;
			query(l,r,1,n,1);
			cout<<mx-mn<<endl;
		}
	}
	return 0;
}

研究了好久,不知道为什么分块总是比线段树快?!

猜你喜欢

转载自blog.csdn.net/KIKO_caoyue/article/details/83413687