您的位置:首页 > 编程语言 > C语言/C++

竞赛题目讲解-【Standard IO】数的划分

2017-07-06 12:04 218 查看

【Standard IO】数的划分

(注册登录后详见:NOI在线题库:数的划分问题1

题目描述

把正整数N分解成M个正整数的和,即使M个数相同但顺序不同也认为是不同的方案,要求总方案数。如3=1+2跟3=2+1是两个不同的方案。

输入

第一行包含两个整数N和M(1<=M<=N<=50)。

输出

输出一个数表示方案数。

样例输入

3 2


样例输出

2


数据范围限制

1<=M<=N<=50

题目解析

一、部分一——从深度优先搜索到记忆化搜索

如果只看这道题的描述,我们可能认为是一道深度优先搜索题,但是再看数据范围(虽然数据小但运算量惊人的大),明显用深度优先搜索会超时,那么广度优先搜索如何?然而数据之间并没有很强的连续性,所以广度优先搜索难以写出代码。那要如何写,这就要涉及到搜索的优化——记忆化搜索。记忆化搜索其实是深度优先搜索的一种,所以我们能够发现它的外部框架完全是深度优先搜索。它适用于许多数据中等的题,我们可以用数组(一维,二维甚至是三维,四维)来储存一些用公式连接起来的数据,其维数通常是涉及到的数据数量,每一维分别表示一个数据

那么这道题用记忆化搜索怎么写呢?我们可以把深度优先搜索搜索改一下(深度优先搜索的源代码见文章尾部的程序样例),即“添加记忆”,一般来说是设置一些边界,通过这些边界求出各个元素的值,储存在容器(多为数组)中,若下次再次访问到这个元素,就直接调用容器中已有的值就行了。我们可以简单分析一下边界——

1. 因为是要拆分为正整数,所以被拆分数首先要是个正整数,也就是被拆分数≥1。所以第一个边界是当被拆分数<1时,返回0

2. 若只要求拆分出1个数,则必定是被拆分数本身。则第二个边界是当要求拆分的数量为1时,返回1

3. 如果被拆分数和要求拆分出的数相等,则只能全部拆为1。则第三个边界为当被拆分数和要求拆分的数量相等,返回1

然后因为是记忆化搜索,我们就用一个二维数组(F)作为容器。简单分析可以得出F[i][j]等于它所有分支的和。得出递归式-

F[i][j]=F[i-1][j-1]+F[i-2][j-1]+...+F[1][j-1]。


将递归式代入递归,然后储存入F数组里,调用并输出。

二、部分二——从记忆化搜索到动态规划

其实这道题在OJ里是分在动态规划的,也就是说它实际上是用动态规划来解题的。学过动态规划的同学可能知道,记忆化搜索其实也是动态规划的一种,它的形式类似于递归(有时候它也可以写成记忆化递归)。而众所周知,只要能用递归做的题递推也能做,从记忆化搜索到动态规划其实就是从递归到递推

首先我们要将记忆化搜索中的边界以预处理的形式表现出来,然后我们分析一下记忆化搜索的作用——从一处倒退,分步求出每一个位置的值,最终得出答案。也就是说记忆化搜索是把所有位置的值求出来的方式,若要递推,我们可以从矩阵(数组)的左上角开始遍历(Tab:从左上角开始的原因一是方便,二是与已知边界值更加接近),根据预处理的边界依次推出各个元素的值,注意若该元素已经计算过(预处理过),则不要再次计算。

三、部分三——优化处理

说了这么多,我们最后也只输出一个答案,为何不把矩阵中所有元素的值都输出来呢(左上角开始编号)?

1|1
2|1 1
3|1 2 1
4|1 3 3 1
5|1 4 6 4 1
6|1 5 10 10 5 1
.|. . . . . . .
.|. . . . . . .
.|——————————————————
n 1 2 3 4 5 6 . . .


我们乍一看没有什么规律,但是变成这样…



!?这不是杨辉三角形吗

你也许会说:只是巧合。但实际上并不是,若我们设F(i,j)表示第i层第j个数的值,则我们知道 F(n,1)|n>0 为1是边界值,则我们知道:

F(i,j)=F(i-1,j-1)+F(i-2,j-1)+...+F(1,j-1)|j-1>0


例如F(6,4)=10,我们会发现:

F(6,4)=F(5,3)+F(4,3)+F(3,3)
=F(4,2)+F(3,2)+F(2,2)+F(3,2)+F(2,2)+F(2,2)
=F(3,1)+F(2,1)+F(1,1)+F(2,1)+F(1,1)+F(1,1)+F(2,1)+F(1,1)+F(1,1)+F(1,1)
=F(3,1)+3*F(2,1)+6*F(1,1)
=1+3+6
=10


即杨辉三角形的另一种求法。因此,我们可以预处理出一个杨辉三角形,然后输出-输入所对应的值(F[m]
)。

这个样例程序就不给出了,大家多加思考写出来提交一下吧(网站在最上面)。

题外话

作者其实也是刚学动态规划,感觉编程的世界多么奇妙。原来40多行的搜索,动态规划几个for循环就搞定了,但是作者确实对动态规划不熟,如果文章中有解释不清的请大家谅解。

又及,我们老师叫我们写一个深度优先搜索的代码,叫我们必须弄出一个TLE(时间超限)来,可怜我的正确率啊…

程序样例

一、明知道要超时还偏要提交的深度优先搜索

#include<cstdio>
int n,m,ans;
void flag(int num,int need)
{
if(need==0 && num==0)
{
ans++;
return;
}
if(need==0 || num==0) return;
for(int i=1;i<=num;i++)
flag(num-i,need-1);
}
int main()
{
scanf("%d%d",&n,&m);
flag(n,m);
printf("%d\n",ans);
return 0;
}


二、把正确率赚回来了的记忆化搜索

/* Lucky_Glass */
#include<cstdio>
int n,m;long long F[55][55];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(j==1 || i==j)
F[i][j]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(i>=j)
{
int t=i-j+1;
if(F[i][j]) continue;
for(int k=1;k<=t;k++)
F[i][j]+=i-k>=1? F[i-k][j-1]:0;
}
printf("%lld\n",F
[m]);
return 0;
}


三、3个不同版本的动态规划——团队合作的杰作

1.

/* Lucky_Glass */
#include<cstdio>
int n,m;long long F[55][55];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=50;i++)
F[i][i]=F[i][1]=1;
for(int j=1;j<=m;j++)
for(int i=1;i<=n;i++)
if(!F[i][j])
for(int k=1;k<i;k++)
F[i][j]+=F[i-k][j-1];
printf("%lld\n",F
[m]);
return 0;
}


2.

/* Lucky_Glass */
#include<cstdio>
int n,m;long long F[55][55];
int main()
{
scanf("%d%d",&n,&m);
F[0][0]=1;
for(int j=1;j<=m;j++)
for(int i=1;i<=n;i++)
for(int k=1;k<=i;k++)
F[i][j]+=F[i-k][j-1];
printf("%lld\n",F
[m]);
return 0;
}


3.

/* Lucky_Glass */
#include<cstdio>
int n,m;long long F[55][55];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=50;i++)
F[i][i]=F[i][1]=1;
F[0][0]=1;
for(int j=1;j<=m;j++)
for(int i=1;i<=n;i++)
if(!F[i][j])
for(int k=1;k<i;k++)
F[i][j]+=F[i-k][j-1];
printf("%lld\n",F
[m]);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法 竞赛算法 C++