周总结#3-递推

思想简述:

递推思想,总结来说就是“大事化小”。如果一件事需要很多步,因而完成这件事有很多方法,需要我们求解这些方法的数量,列举这些方法,或者找出其中最优的方法的时候,我们就要用到递推思想。比如说,走楼梯:我们可以1步跨一个台阶,也可以1步跨2个台阶,问走1000个台阶总共有多少种走法。这个问题,我们首先想到可以暴力枚举,但是所用的时间过长。其次,我们把问题简单化,就想最后一步可能是已经走了998个台阶然后一步跨两个台阶上去,也可能是走了999个台阶,最后一步只跨一个台阶上去。因此问题缩小到走998和999个台阶共有多少种走法,这是我们会发现,求998或者999个台阶走法和求1000个台阶走法并无本质差别,也就是设走i个台阶有f[i]种走法,则f[1000]=f[999]+f[998], f[999]=f[998]+f[997],f[998]=f[997]+f[996]......f[3]=f[2]+f[1]直到f[2]和f[1]的情形,再也求不出来了,递归也就到了它的边界情况,而边界情况一旦确定,这个递归过程倒过来所有经历过的过程f[i]就全都能求出来了。

像上面那个例子,把一个复杂过程回溯为更简化更接近初始状态的过程,后面的步骤数由前面的(或者子部分)步骤数决定,并且每一步可以用前一步类比,因而用相同的公式求解,这样的的就是利用了递推的思想。

考虑递推思想有三大重要的要素,递推关系,递推范围,边界情况(初始情况)

不要忽略只有列举起来比较复杂庞大的问题才用得着递归,要不然直接暴力枚举好了。所以,需要注意,递推范围过大的时候,我们可以用矩阵加速提升效率。

在这里面有两个比较经典的递推模型,分别是斐波那契卡特兰数,它们某项的递推公式都是建立在前面项数基础上的。

其中,卡特兰数的应用场景比起斐波那契数列增加了双重可能性,即乘法原理,这两种重要数列的应用场景和区别,我们将会在以下的例题讲解当中更清楚地认识到。

经典问题:

P1028 [NOIP2001 普及组] 数的计算

简要思路:

合法数列除n本身之外,n后面还可以有以n/2为及以下的数列开头的数列,这些可以用求n开头合法数列同种方法求到。

递推公式:f[n]=1+f[1]+f[2]+f[3]+...+f[n/2]

递推范围:由于f[n]知道的前提是f[i](i<=n/2)都知道,所以递推范围是1~n/2

边界条件:f[1]=1

源代码如下:

P1192 台阶问题(斐波那契

简要思路:

考虑最后一步到达顶端的情形,可能跨过的台阶数是从1~k当中任意一个数,而1~k当中的任意一个i对应着一个前面台阶数以及走法,于是:

递推公式:f[i]=f[i-1]+f[i-2]+...+f[i-k];

递推范围:想要知道f[n],就得知道f[n-1]到f[n-k]的所有值,由于f[1]~f[k]的值无法用通项公式求出,我们要单独求这一部分所以递推范围包括两部分:1 从f[1]~f[k]  2 从f[k+1]~f[n]

边界条件:

f[1]=1;

f[1]~f[k]算法:最后一步可能是1~i中的任何一个情形,也递推算就可以了

代码如下:

#include <iostream>
using namespace std;

int n,k;
int f[100010];

int main(){
	scanf("%d%d",&n,&k);
	f[0]=f[1]=1;
	for(int i=2;i<=n;i++){
		if(i<=k) f[i]=(f[i-1]*2)%100003;
	else f[i]=(f[i-1]*2-f[i-k-1])%100003;
	}
	  //会有f[0]出现
	printf("%d",(f[n]+100003)%100003);
	return 0;
}

总结:

后面的数等于前面的数相加得到,这是一种复合类斐波那契数列的模型。

使用斐波那契数列或者类似斐波那契数列这样前面相加得到后面的数列的时候,这种数列的增长速度一定是很快的,所以该开高精度的时候就开高精度。

P1044 [NOIP2003 普及组] 栈(卡特兰数

(题目比较长 因此这里只节选了一部分)

简要思路:

设想一个栈里面与n个元素,这n个元素进出栈的方法有很多,到最后一步的时候,我们让k出栈(1<=k<=n),那前面的出栈入栈情况有多少种? 首先k一定是第k个入栈的(栈中存放着连续整数1~n),而在k入栈之前前面,k-1个元素又进行了入栈并全部出栈的过程,而k后面的那些在k入栈之后进栈,并全部出栈,由于方式本身和入栈出栈元素大小没有关系,所以k-1个元素与后面n-k个元素入栈出栈的情况又可以简化成h[k-1]与h[n-k]了,求法竟然和h[k]相同(设h[i]为i个元素出栈入栈的方法数),而1~k项出栈入栈与k+1~n项出栈入栈满足乘法原理,最后不同出栈元素之间满足加法原理,所以

递推公式:

h[i]=h[0]*h[n-1]+h[1]*h[n-2]+...+h[n-1]*h[0]

边界情况(这个应该比范围更加先考虑):h[0]=h[1]=1;

递推范围:想要知道h[n]就得知道从h[0]到h[n-1]的所有,因此范围是从2到n

源代码如下:

#include <iostream>
using namespace std;

int n;
int f[20];

int main(){
	scanf("%d",&n);
	f[0]=f[1]=1;
	for(int i=2;i<=n;i++){
		for(int j=0;j<i;j++){
			f[i]+=f[j]*f[i-j-1];			
		}
	}
	printf("%d",f[n]);
	return 0;
}

总结:

实际上,这个问题所用上的是卡特兰数,卡特兰数的前几项是1 1 2 5 14 42 132 429 1430 4862 ,要对这几个数字敏感。卡特兰数有很多的性质,有待我们深入探讨。

从上面两例,我们得以窥见斐波那契数列和卡特兰数的应用场景区别,从而窥见这两种递推模型会出现的本质原因。

斐波那契模型和类斐波那契模型求解的事件步骤之间满足的是一定的时间先后顺序,也就是后面的事件之所以会发生,是因为有前面n-1件事堆叠而成,最终那n-1件事将事件推入第n件事所在的情形,因此,当第n件事有很多种做法时,制约用哪种做法达成相同目的的是第n-1步的事态而这一事态又包含了前n-1个步骤的做法。

卡特兰就不一样了,在卡特兰所涉及的情景里面,比如说分割多边形,多点连线问题,台阶问题,唱票问题,以及本节栈的问题,时间顺序变得无关紧要(虽然时间在有些问题里面还是存在的),而决定问题解法的是空间分割的方法。事件的发生遵循某一种规则,是由于它被分割成了的那两个部分临界点所决定的,处理两部分(或者两个以上部分也可以)的方法有点像分治。

P1003 [NOIP2011 提高组] 铺地毯

这是一道模拟题

重在考察数学建模过程,把每一点的地毯编号给这一点空间赋值(前提是地毯编号就是1~i),这些点就组成了二维数组, 输入的地毯边界仍然是四个数组,每铺上一层地毯实际上是吧地毯设计到的那部分数组更新一遍,从而整个数组(地)得到更新。

但是,在此基础上,时间空间仍可以优化,首先我们只是求一个地毯上的值,别的根本就不需要每次都更新一遍,也不需要整个地的数据,我们只需要确保这个我们要求得点,是否在每次的更新范围内即可,在遍历1~n的地毯更新情况,如果在更新地毯范围内,就把点上的地毯编号更新为当前地毯编号。

这个点的初始值是-1

这样,我们就省去了存储整个地毯的数组,以及将值一个一个付给需要更新的所有点的时间。

方便起见,四个地毯参数分别放到四个不同的数组里面。

如果是用最初哪种全盘模拟的情况:可能需要这个函数

这个函数是能全盘赋值的

Memset (a,int b, sizeof(a));

代码如下:

#include <iostream>
#include <cstring>
using namespace std;

const int N=1e4+5;

int n;
int a[N],b[N],g[N],k[N];

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d%d%d%d",&a[i],&b[i],&g[i],&k[i]);
	}
	int res=-1;
	int x,y;
	scanf("%d%d",&x,&y);
	for(int i=1;i<=n;i++){
		if(x>=a[i] && x<=a[i]+g[i] && y>=b[i] && y<=b[i]+k[i]){
			res=i;
		}
	}
	printf("%d",res);
	return 0;
	//每次判断是否在巨型里面
}

#include <iostream>
using namespace std;

int n, f[1005];

int main(){
	scanf("%d",&n);
	f[1]=1;
	for(int i=2;i<=n;i++){ 
		for(int j=1;j<=i/2;j++){ 
		f[i]+=f[j];
		}
		f[i]++;
	}
	printf("%d",f[n]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值