分块思想

今天学习了一个算法(这个应该叫做算法吧?)叫做分块(和莫队,但是莫队还没有搞懂,搞懂再来写吧)
听起来很高级,蒟蒻表示瑟瑟发抖。但是学完发现怎么那么像是一种变相的暴力呢。
分块思想:假如你要处理一个很长区间上的问题,并且有很多个查询(以及修改),暴力显然已经不可行了。但是暴力效率低下的主要原因是因为他没有很好的利用每次处理后得到的信息而选择重新处理,而进行优化的关键就是如何有效的储存每次处理后的信息。对于线段树来讲是将这个区间进行好多次二分变成一个树进行储存,大区间就储存着每次处理后的信息从而提升效率,对于树状数组来说它同样是将区间分成了许多小部分,用的不是二分而是对二进制的一种应用形成的树状结构。而我们能不能什么高级的数据结构都不使用来解决这种问题呢?答案就是朴素的分块思想——如果区间比较大,就分成一个一个小段来处理,用一个数组来保存每一个小段的信息,然后在具体询问的时候如果询问的区间大于我们分的块就能够使用我们保存过的数据提高效率,如果小于就直接暴力应该也不会很大。如果大于我们分的块但又不是整块怎么办呢?就将两端不是整块的进行暴力,应该也不会很大(至少可以接受)
这里附一道比较有趣的分块题(我做的第一道分块题):BZOJ2002
题意大概是你有一只绵羊(不要问我为什么是只羊而不是兔子),然后它可以在许多弹簧上跳,现在给你在每个弹簧上能够向后跳多远,问你它跳多少次后绵羊就掉下去。而且还会修改弹簧的弹力。
对于这个问题很容易有一个暴力的思想就是我们对于每次询问就直接让他跳就好了,每次修改就改就好了,显然会爆。
有一种比较巧妙的从后往前跳一边然后储存这个信息以后就能直接使用(从后往前是因为他是向后跳的,所以只会被后面的值影响),但是每次修改又会变成一个比较复杂的问题。
所以我们折中的使用分块,就是将原来的大区间分成许多小区间(一般来讲是sqrt(n)不要问我为什么,我也不知道,好像是什么基本不等式推出来比较好),然后每次更新小区间的值,查询的时候每次用小区间的值。
具体的做法是用两个数组,一个储存跳出当前块需要多少步(求答案的时候直接加起来),一个储存跳出这个块后会跳到下个块的什么位置。从后往前进行初始化。然后对于每次更新只在块内更新,对块外的没有影响。
详细看注释:

#include<cstdio>
#include<cstring>
#include<cmath>
#define ll long long
#define belong(x) ((x-1)/block+1)
using namespace std;

const int maxn=200005;
int n,m,block;
int a[maxn],pos[maxn],step[maxn];

int ask(int x)
{
	int ans=0;
	while(x!=-1)
	{
		ans+=step[x];
		x=pos[x];
	}
	return ans;
}
int main()
{
	memset(a,0,sizeof(a));
	memset(pos,0,sizeof(pos));
	memset(step,0,sizeof(step));
	scanf("%d",&n);
	block=(int)sqrt(n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	for(int i=n;i>0;i--)	//从后往前初始化
	{
		if(i+a[i]>n)
		{
			pos[i]=-1; step[i]=1;	//如果直接跳出整个区间了标记位置为-1
		}
		else if(i+a[i]>belong(i)*block)
		{
			pos[i]=i+a[i]; step[i]=1;	//记录跳到下一个块的什么位置
		}
		else
		{		//如果没有跳出去就会一直跳到i+a[i]跳出去的位置,用的步数等于i+a[i]用的步数+1
			pos[i]=pos[i+a[i]];	
			step[i]=step[i+a[i]]+1;
		}
	}
	scanf("%d",&m);
	int cmd,u,v;
	for(int i=0;i<m;i++)
	{
		scanf("%d",&cmd);
		if(cmd==1)
		{
			scanf("%d",&u);
			u++;
			printf("%d\n",ask(u)); 
		}
		else if(cmd==2)
		{
			scanf("%d%d",&u,&v);
			u++;	//我们这里是从1开始的,而问题是从0开始的,请注意
			a[u]=v;
			int left=(belong(u)-1)*block;
			for(int i=u;i>left;i--)
			{
				if(i+a[i]>n)
				{
					pos[i]=-1;
					step[i]=1;
				}
				else if(i+a[i]>belong(i)*block)
				{
					pos[i]=i+a[i];
					step[i]=1;
				}
				else
				{
					pos[i]=pos[i+a[i]];
					step[i]=step[i+a[i]]+1;
				}
			}
		}
		
	}
	return 0;
}

就酱 ^_ ^

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值