今天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 | |
2 | 1 |
3 | 7 |
4 | 4 |
5 | 2,3,5 |
6 | 6,9 |
7 | 8 |
火柴数 | 可能数字 |
x+0 | |
x+1 | |
x+2 | 1 |
x+3 | 7 |
x+4 | 4 |
x+5 | 2,3,5 |
x+6 | 0,6,9 |
x+7 | 8 |
从前往后每添加一个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的余数。
分析