划分数问题——盘子与小球——排列组合——递推式思维转化

2024-03-11 01:20

本文主要是介绍划分数问题——盘子与小球——排列组合——递推式思维转化,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

0#引入

考试时遇到同球同盘的问题,一开始想用插板法,终究失败,只拿了20分,回家问了老师,老师告了我方法并让我自己研究剩下三种。

1#同球同盘——递推或递归(+记忆化改进优化)

这个我有真题http://www.kencoding.net/problem.php?id=1369,大家可以去做一下

我先把递推公式列在这里:
(n是球数,m是盘子数)
{ f ( 0 , 0 ) = 1 f ( n , 1 ) = 1 f ( n , m ) = f ( n , n ) i f ( n < m ) f ( n , m ) = f ( n − m , m ) + f ( n , m − 1 ) i f ( n ≥ m ) \left\{ \begin{array}{ll} f(0,0)=1\\ f(n,1) = 1\\ f(n, m)=f(n,n) \ \ \ if(n < m)\\ f(n,m) = f(n - m, m) + f(n, m-1)\ \ \ if(n\ge m)\\ \end{array} \right. f(0,0)=1f(n,1)=1f(n,m)=f(n,n)   if(n<m)f(n,m)=f(nm,m)+f(n,m1)   if(nm)

第一个是人为修正的,后两个是可以理解的,第三个我来解释一下:

如果n大于等于m时,那么分为两种情况(1)放满盘子;(2)有空盘。

(1) f ( n − m , m ) f(n-m,m) f(nm,m)的意思是:既然要将球放满,那么就从每个盘子里拿走一个球,即球数减盘子数,剩下的球在其中继续排列结果是不变的,因为同球同盘嘛。如果第一次每个盘子取一个之后球数仍然大于盘子数,那么将会在第二次再次执行每个盘子去一个球的操作。
(2) f ( n , m − 1 ) f(n,m-1) f(n,m1)的意思是:有空盘的话,就先拿走一个盘子,剩下的球在剩下的盘子中排列,直到盘子数减到1。

0的特殊情况:我们来看一个实例,按照我们的公式,f(2,2)会执行如下操作:

f ( 2 , 2 ) = f ( 0 , 2 ) + f ( 2 , 1 ) = f ( 0 , 0 ) + 1 = ? + 1 f(2,2)=f(0,2) + f(2,1)=f(0,0) + 1 = ? + 1 f(2,2)=f(0,2)+f(2,1)=f(0,0)+1=?+1

我们实际算一下,f(2,2)应该是2,那么f(0,0)是多少呢???只能是一了,所以我就这么尴尬的定下来了。

下面我给出代码,这道题可以AC该题目:

(1)递推版本:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int n, m, p;
int dp[1010][1010];
int main()
{cin >> n >> m >> p;for (int i = 1; i <= 1001; i++){dp[i][1] = 1;// dp[0][i] = 1;}for (int i = 1; i <= n; i++){for (int j = 1; j <= m; j++){if (i < j)dp[i][j] = dp[i][i];else if (i > j){dp[i][j] = (dp[i][j - 1] + dp[i - j][j]) % p;}else if (i == j){dp[i][j] = (1 + dp[i][j - 1]) % p;}}}cout << dp[n][m] << endl;return 0;
}

严谨一些我们可以加上一些初始化比如memset

(2)递归版本,会超时

#include <iostream>#include <cstring>
#include <cstdio>using namespace std;
int p;
int f(int n, int m)
{if (m == 1)return 1;if (n == 0)return 1;if (m > n)return f(n, n) % p;if (m <= n){return (f(n, m - 1) + f(n - m, m)) % p;}
}int main()
{int n, m;cin >> n >> m >> p;cout << f(n, m) % p << endl;return 0;
}

为什么会超时呢?这就像我们的经典例题斐波那契数列了,我们会发现有很多的重复调用,所以我们需要用数组来解决问题,也就是记忆化搜索,而改变为记忆化搜索之后递归也就从根本上和递推是一个道理了,可以统称为动态规划了。

(3)数组改版递归,记忆化搜索

#include <iostream>#include <cstring>
#include <cstdio>using namespace std;
int p;
int dp[1010][1010];
int f(int n, int m)
{if (m == 1)return 1;if (n == 0 && m == 0)return 1;if (m > n)return f(n, n) % p;if (m <= n && dp[n][m] == -1){dp[n][m] = (f(n, m - 1) + f(n - m, m)) % p;return dp[n][m];}else{return dp[n][m];}
}int main()
{int n, m;memset(dp, -1, sizeof(dp));for (int i = 1; i <= 1001; i++){dp[i][1] = 1;dp[0][i] = 1;}cin >> n >> m >> p;cout << f(n, m) % p << endl;return 0;
}

AC散花。

错误总结:
不能忘记取模的优先级,一定要加括号,一直因为这个小问题耽误了60分!!!

2#异球同盘——递归

n个各不相同的球放在m个相同的盘子里,问所有情况有多少种?

递推(归)公式:

这个递推公式是用来算全放满的可能性的
{ f ( n , 1 ) = 1 f ( n , 0 ) = 1 f ( n , m ) = 1 i f ( n = m ) f ( n , m ) = f ( n − 1 , m − 1 ) + f ( n − 1 , m ) × m i f ( n ≥ m ) \left\{ \begin{array}{ll} f(n,1)=1\\ f(n,0) = 1\\ f(n, m)=1 \ \ \ if(n = m)\\ f(n,m) = f(n - 1, m - 1) + f(n-1, m)\times m\ \ \ if(n\ge m)\\ \end{array} \right. f(n,1)=1f(n,0)=1f(n,m)=1   if(n=m)f(n,m)=f(n1,m1)+f(n1,m)×m   if(nm)

有了这个递推公式,那么就可以把盘子数从小到大循环上去,每次调用一个对应的函数,然后累加结果,就是答案了。

解释:

(1) f ( n , 1 ) = 1 f(n,1)=1 f(n,1)=1 f ( n , 0 ) = 1 f(n,0)=1 f(n,0)=1可以理解
(2) f ( n , m ) = 1 f(n,m)=1 f(n,m)=1因为必须全放满,而且因为是异球同盘,所以n=m时只能有一种情况。
(3)最后的关系式:分为两个步骤,首先我们先去掉一个球和一个盘子,就是f(n-1,m-1),使得单独的一个小球与盘子独立,然后算剩下的小球在剩下的盘子里面的排法;但是这样不能使得排法全面,所以我们暂时将盘子放回来,变成m个盘子,小球还在外面,算n-1个球在m个盘子中的排法,为什么要乘m?因为最后一个小球还要放进来,小球可以放在m个位置,而没放在一个位置,都会对应着所有的排法的改变,故乘m。为什么要这么干呢?因为这样可以使得状态逐步向n<m发展,也能计算有堆叠球的情况,这就是状态转移的概念。

/*
问题描述:
n个不同的球放在m个同样的盘子中
允许空盘:(大于) 分别计算0个、1个、2个...n-1个空盘的数值,并求和 
不允许空盘:题解函数 
盘子相同的情况下,如果盘子比球多,则忽略多余的盘子 
解法思路:根据第一个小球的情况分情况递归(独自一盘,与它球共处一盘) 
*/
#include <iostream>
#include <algorithm>
using namespace std;
int ballPlate2(int ballNum, int plateNum)
{if (plateNum == 1){return 1;}if (plateNum < 1){return 0;}if (ballNum == plateNum){return 1;}if (ballNum < plateNum){return 0;}int a1, a2;a1 = ballPlate2(ballNum - 1, plateNum - 1);a2 = ballPlate2(ballNum - 1, plateNum);return a1 + a2 * plateNum;
}
int ballPlate2_empty(int ballNum, int plateNum)
{int sum = 0;for (int i = 0; i <= plateNum; i++){int a = ballPlate2(ballNum, i); // 盘子数不断增加算每一种sum += a; // 累加结果,即为所有结果}return sum;
}
int main()
{cout << ballPlate2(4, 3) << endl; // 全放满的结果cout << ballPlate2_empty(4, 3) << endl; // 所有结果return 0;
}

也可以加上记忆化优化。。。

3#同球异盘——插板法

这道题倒是用不到递推,我们可以用简单的排列组合插板法解决

也就是将m-1个板子插到n-1个空档中。用程序中的full_f()函数解决,但这个只能算出全放满的可能性。

要算出所有结果,我一开始写的是循环,但是错了,其实有一个非常简单的方法直接去算(n + m - 1, m - 1)的插板就行了。我们可以这么理解,插板法都是插在球的内部,那为了插出空盘,就可以将空档增加,增加到盘子外面,这样就有了额外的空档,可以插出空盘,也可以插出全满。

在这里插入图片描述

#include <iostream>using namespace std;
int n, m;
int full_f(int n, int m)
{int a = 1;for (int i = 1; i <= m; i++){a *= (n - i + 1);}for (int i = 1; i <= m; i++){a /= i;}return a;
}
int d(int n, int m)
{return full_f(n + m - 1, m - 1);
}
int main()
{cin >> n >> m;cout << full_f(n - 1, m - 1) << endl; // 放满结果cout << d(n, m) << endl; //所有结果return 0;
}

4#异球异盘——最简单的mn

既然是异球异盘,我们可以看出来每一个球都有m个选择,每个球还不怕重复,所以n个球m个盘子,那么结果就是mn

#include <iostream>
#include <math.h>
using namespace std;
int binPow(int n, int m) // 快速幂
{int result = 1;while (m != 0){if (m % 2 != 0)result *= n;n *= n;m /= 2;}return result;
}
int fac(int n) // 阶乘factorial
{int ans = 1;for (int i = 1; i <= n; i++){ans *= i;}return ans;
}int f(int n, int m) // 这是异球同盘的程序
{if (m == 1)return 1;else if (m == 0)return 0;if (n == m)return 1;else if (n < m)return 0;if (n >= m){int a1 = f(n - 1, m - 1);int a2 = f(n - 1, m) * m;return a1 + a2 * m;}
}int bp_empty(int ballNum, int plateNum)
{return binPow(plateNum, ballNum);
}
int bpf(int ballNum, int plateNum)
{int b = f(ballNum, plateNum);int a = fac(plateNum);return a * b; // 将盘子的排法和异球同盘相乘
}int main()
{int n, m;cin >> n >> m;cout << bpf(n, m) << endl; // 全放满cout << bp_empty(n, m) << endl; // 所有情况,m^nreturn 0;
}

5#总结

这四个题目的程序思维都不一样,都需要思考很久,有时想了很久都想不出来,出去休息一会回来突然就有了新的思路。。

这篇关于划分数问题——盘子与小球——排列组合——递推式思维转化的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/796140

相关文章

mybatis和mybatis-plus设置值为null不起作用问题及解决

《mybatis和mybatis-plus设置值为null不起作用问题及解决》Mybatis-Plus的FieldStrategy主要用于控制新增、更新和查询时对空值的处理策略,通过配置不同的策略类型... 目录MyBATis-plusFieldStrategy作用FieldStrategy类型每种策略的作

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

Python Jupyter Notebook导包报错问题及解决

《PythonJupyterNotebook导包报错问题及解决》在conda环境中安装包后,JupyterNotebook导入时出现ImportError,可能是由于包版本不对应或版本太高,解决方... 目录问题解决方法重新安装Jupyter NoteBook 更改Kernel总结问题在conda上安装了

pip install jupyterlab失败的原因问题及探索

《pipinstalljupyterlab失败的原因问题及探索》在学习Yolo模型时,尝试安装JupyterLab但遇到错误,错误提示缺少Rust和Cargo编译环境,因为pywinpty包需要它... 目录背景问题解决方案总结背景最近在学习Yolo模型,然后其中要下载jupyter(有点LSVmu像一个

解决jupyterLab打开后出现Config option `template_path`not recognized by `ExporterCollapsibleHeadings`问题

《解决jupyterLab打开后出现Configoption`template_path`notrecognizedby`ExporterCollapsibleHeadings`问题》在Ju... 目录jupyterLab打开后出现“templandroidate_path”相关问题这是 tensorflo

如何解决Pycharm编辑内容时有光标的问题

《如何解决Pycharm编辑内容时有光标的问题》文章介绍了如何在PyCharm中配置VimEmulator插件,包括检查插件是否已安装、下载插件以及安装IdeaVim插件的步骤... 目录Pycharm编辑内容时有光标1.如果Vim Emulator前面有对勾2.www.chinasem.cn如果tools工

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

Java多线程父线程向子线程传值问题及解决

《Java多线程父线程向子线程传值问题及解决》文章总结了5种解决父子之间数据传递困扰的解决方案,包括ThreadLocal+TaskDecorator、UserUtils、CustomTaskDeco... 目录1 背景2 ThreadLocal+TaskDecorator3 RequestContextH

关于Spring @Bean 相同加载顺序不同结果不同的问题记录

《关于Spring@Bean相同加载顺序不同结果不同的问题记录》本文主要探讨了在Spring5.1.3.RELEASE版本下,当有两个全注解类定义相同类型的Bean时,由于加载顺序不同,最终生成的... 目录问题说明测试输出1测试输出2@Bean注解的BeanDefiChina编程nition加入时机总结问题说明

关于最长递增子序列问题概述

《关于最长递增子序列问题概述》本文详细介绍了最长递增子序列问题的定义及两种优化解法:贪心+二分查找和动态规划+状态压缩,贪心+二分查找时间复杂度为O(nlogn),通过维护一个有序的“尾巴”数组来高效... 一、最长递增子序列问题概述1. 问题定义给定一个整数序列,例如 nums = [10, 9, 2