奋斗的demon——递推关系(理论)

今天demon分析了一下之前的组队赛,觉得有些心得:

我们这次比赛没有关注文件读写问题,导致我们一直纠结于提交出错的情况,请你大家在比赛之前了解文件读写的相关规定:是标准输入输出,还是文件输入输出?如果是文件输入输出,是否禁止用重定向方式访问文件?

program (重定向版)

 

#define LOCAL
...
int main()
{
#ifdef LOCAL
	freopen("data.in","r",stdin);
	freopen("data.out","w",stdout);
#endif
	...	
}

 

program (fopen版)

 

#include <stdio.h>
int main()
{
	FILE *fin,*fout;
	fin = fopen("data.in","rb");
	fout = fopen("data.out","wb");
	int x;
	fscanf(fin,"%d",&x);
	fprintf(fout,"%d\n",x);
	fclose(fin);
	fclose(fout);
}

 

 

 

 

 

今天大白老师给demon讲递推关系了,呆萌的demon觉得很有趣,大家一起来体会递推的乐趣吧!

问题1:兔子的繁殖。把雌雄各一的一对新兔子放入养殖场中。每对兔子从第二个月开始每月产雌雄各一的一对新兔子。试问第n个月后养殖场中共有多少对兔子?

分析:第n个月的兔子由两部分组成,一部分是上个月就有的老兔子,一部分是刚出生的新兔子。前一部分等于f(n-1),后一部分等于f(n-2) (第n-1个月时具有生育能力的兔子数等于第n-2个月的兔子总数)。根据加法原理,f(n)=f(n-1)+f(n-2),边界是f(1)=f(2)=1。

数列为1,1,2,3,5,8,...,称为斐波那契(Fibonacci)数列。

问题2:凸多边形的三角剖分数。给出一个凸n边形,用n-3条不相交的对角线把它分成n-2个三角形,求不同的方法数目。例如n=5时,有5中剖分方法。

分析:设答案为f(n)。按照某种顺序给凸多边形的各个定点分别编号为v1,v2,...,vn。既然分成的是三角形,边v1vn在最终的剖分中一定恰好属于某个三角形v1vnvk,所以可以根据k进行分类。不难看出,三角形v1vnvk的左边是一个k边形,右边是一个n-k+1边形,根据乘法原理(参照奋斗的demon——基本计数原理(理论)),包含三角形v1vnvk的方案数为f(k)f(n-k+1);根据加法原理有:f(n)=f(2)f(n-1)+f(3)f(n-2)+...+f(n-1)f(2),边界是f(2)=f(3)=1。

从第三项开始的数列:1,2,5,14,42,132,429,1430,4862,16796,...,称为卡特兰数(Catalan)。

因为大家普遍对斐波那契数列比较熟悉,对卡特兰数不太熟,所以这里补充一些卡特兰数的性质:

 

1 通项公式:h(n)=C(n,2n)/(n+1)=(2n)!/((n!)*(n+1)!)

2递推公式:h(n)=((4*n-2)/(n+1))*h(n-1); h(n)=h(0)*h(n-1)+h(1)*h(n-2)+...+h(n-1)*h(0).

3前几项为:h(0)=1,h(1)=1,h(2)=2,h(3)=5,h(4)=14,h(5)=42,......

4应用场景:

a.括号化问题
  矩阵链乘: P=a1×a2×a3×……×an,依据乘法结合律,不改变其顺序,只用括号表示成对的乘积,试问有几种括号化的方案?(h(n)种)
b.出栈次序问题。
  一个栈(无穷大)的进栈序列为1,2,3,..n,有多少个不同的出栈序列?
  类似:
  (1)有2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票,问有多少中方法使得只要有10元的人

      买 票,售票处就有5元的钞票找零?(将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈)
  (2)在圆上选择2n个点,将这些点成对连接起来,使得所得到的n条线段不相交的方法数。

c.将多边行划分为三角形问题
  (1)将一个凸多边形区域分成三角形区域的方法数?
  (2)类似:一位大城市的律师在她住所以北n个街区和以东n个街区处工作。每天她走2n个街区去上班。如果她从不穿越(但可以碰到)从家到办公室的对角线,那            么有多少条可能的道路?
  (3)类似:在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数?
d.给顶节点组成二叉树的问题。
  给定N个节点,能构成多少种形状不同的二叉树
  (一定是二叉树!先去一个点作为顶点,然后左边依次可以取0至N-1个相对应的,右边是N-1到0个,两两配对相乘,就是

      h(0)*h(n-1) + h(2)*h(n-2) +  + h(n-1)h(0)=h(n))(能构成h(N)个)。

问题3:用n(1≤n≤2000)根火柴棍能组成多少个非负整数?火柴不必用完,组成的整数不能有前导零(但整数0是允许的)。例如,假如有三根火柴,可以组成1或者7,假如有四根火柴,除了1和7以外,还可以组成4和11。

我们也不难知道数字0-9的火柴数:1(2),2(5),3(5),4(4),5(5),6(6),7(3),8(7),9(6),0(6)。

分析:把已经用过视为火柴数i看成一个状态,额可以得到一个图。

 

火柴数可能数字
0 
1 
21
37
44
52,3,5
66,9
78
火柴数可能数字
x+0 
x+1 
x+21
x+37
x+44
x+52,3,5
x+60,6,9
x+78


从前往后每添加一个x,就从状态i+c[i],其中c[x]代表数字x需要的火柴数。当i=0的时候不允许使用数字0。从结点0和x出发。

令d(i)为从结点0到结点i的路径条数,而且火柴不必用完,则答案f(n)=d(1)+d(2)+d(3)+...+d(n)。

 

memset(d,0,sizeof(d));//d[i]为恰好用i根火柴可以i组成的正整数(不含0)
d[0] = 1;
for(int i=0;i<=MAXN;i++)
	for(int j=0;j<10;j++)
		if(!(i==0&&j==0) && i+c[j]<=MAXN) d[i+c[j]]+=d[i];//i=j=0时不允许转移

 

问题4:立方数之和。输入正整数n(n≤10000),求将n写成若干个正整数的立方之和有多少种方法。

例如:21有2种写法。77有22种写法,9999有440022018293种写法。

分析:建立多段图。结点(i,j)表示“使用不超过i的整数的立方,累加和为j”这个状态,设d(i,j)为从(0,0)到(i,j)的路径条数,则最终答案为d(21,n)。

这个多段图的特点时每个结点一步只能走到下一阶段的结点,因此我们可以一个一个阶段的计算。

 

memset(d,0,sizeof(d));
d[0][0] = 1;
for(int i=0;i<=MAXN;i++)
	for(int j=0;j<MAXN;j++)
		for(int a=0;j+a*i*i*i<=MAXN;a++)//枚举后继结点(i,j+a*i^3)。保证下标不越界 
			d[i][j+a*i*i*i]+=d[i-1][j]; 

但是这个时间复杂度不好,大家思考一下:

 

对于第i个数字而言,我们可以这样分析:如果我们取一次第i个数字得到d(i,j)那么可以知道这是从状态d(i,j-i^3)转化得到;当我们不取第i个数字得到d(i,j)那么可以知道这是从状态d(i-1,j)转化得到。那么我们便可以得到递推公式:d(i,j)=d(i-1,j)+d(i,j-i^3)(如果大家想说我们如果需要用多个i^3怎么办?是不是这个公式不在成立呢?仔细想想还是成立的,因为我们如果再往下递推一层或者多层我们便可以使用多次i^3,那么我们便可以把代码写成如下形式:时间复杂度仅仅是O(n^2),而且我们可以用滚动数组节约存储空间。

感觉一下这个方法和上面的算法的对比。

 

#include<iostream>
#include<string.h>
#include<math.h>
#include<algorithm>
using namespace std;
#define  MAXN 22*22*22
long long dp[MAXN];
int main()
{
    int n;
    memset(dp,0,sizeof(dp));
    long long c[22];
    for(int i=1;i<22;i++)
        c[i]=i*i*i;
    dp[0]=1;
    for(int i=1;i<22;i++)
        for(int j=c[i];j<MAXN;j++)
        dp[j]+=dp[j-c[i]];
    while(cin>>n)
    {
        cout<<dp[n]<<endl;
    }
}

问题5:村民排队。村子里现在有n(1≤n≤40000)个人,有多少种方式可以把他们排成一列,使得没有人排在他父亲的前面(有些人的父亲可能不在村子里)?输入n和每个人的父亲编号(村里的人编号为1~n),输出方案总数除以1000000007的余数。

分析

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值