E: 整数划分问题题目描述
将正整数 n 表示成一系列正整数之和, n = n 1 + n 2 + . . . + n k ,其中 n 1 ≥ n 2 ≥ n 3 ≥ . . . ≥ n k ≥ 1 , k ≥ 1 。
正整数 n 的这种表示称为正整数 n 的划分,正整数 n 的不同的划分个数称为正整数 n 的划分数,记作 p ( n ) 。
例如,正整数 6 有如下 11 种不同的划分,所以 p ( 6 ) = 11 。
6 = 6;
6 = 5 + 1;
6 = 4 + 2;
6 = 4 + 1 + 1;
6 = 3 + 3;
6 = 3 + 2 + 1;
6 = 3 + 1 + 1 + 1;
6 = 2 + 2 + 2;
6 = 2 + 2 + 1 + 1;
6 = 2 + 1 + 1 + 1 + 1;
6 = 1 + 1 + 1 + 1 + 1 + 1;
输入描述
多组数据,每组数据包含一个正整数 n ( 1 ≤ n ≤ 20 ) 。
输出描述
每组数据输出一行,包含一个正整数 k ,表示正整数 n 的划分数。
样例输入
1
2
样例输出
1
2
题解:
这题课上,老师用的办法就是递归,而且和汉诺塔不一样,它的状态转移方程除了递归“整体法”,分步可以很容易表示出来,这类递归已经几乎就是递推来实现,
直接找规律找不到sum的规律,而且设置sum作为变量来做,状态方程更加麻烦,会有重复筛选的情况,所以用模拟的办法
6 = 6;
6 = 5 + 1;
6 = 4 + 2;
6 = 4 + 1 + 1;
6 = 3 + 3;
6 = 3 + 2 + 1;
6 = 3 + 1 + 1 + 1;
6 = 2 + 2 + 2;
6 = 2 + 2 + 1 + 1;
6 = 2 + 1 + 1 + 1 + 1;
6 = 1 + 1 + 1 + 1 + 1 + 1;
除了多个状态要考虑,这题找规律过程最重要的是发现它是按照什么方式往后面寻找,从而使得情况不会有重复的出现
而且题目的 n=6 例子给了提示,即从5—1,而后面的数必定小于等于前一个数(an >= an+1),极限是最终分解为 n个数
这个是例题,先给出例题的解法—不是直接模拟,而是模拟了搜索树,
但是它不需要遍历到最后,然后把所有叶子节点都记下,
它的办法是计分支数,每次遇到有分支就+1,然后递归下一步,把之后的分支数的返回加起来
—题解的状态转移方程,直接设为二元变量,f(a,b),输出对象结果序列的次数,规定a小于等于b为标准情况
f(n, m) = 1; ( n = 1 or m = 1 )
f(n, n); ( n < m )//分割n本身
1+ f(n, m - 1); ( n = m )//需要有-1操作才能有变化
f(n - m, m) + f(n, m - 1); ( n > m )//
要求
先确定自变量,然后对—拆数方法—分类讨论,拆数的依据是m与n的大小关系_可分为,
- 最终恰好有一个为1,此时找到一个分支
- n=m,但不到1,没到底,令其中一个区间收缩
- 一个是1 ,一个是n-1或m-1,
- 以及最难想的一种情况4 ,一个是m和n-m,刚好互补的关系,然后,另一个n不变,m变
在递归的过程中,不断分解成两块,但是它是两个变量都是自动处理的,
有点类似于”二分搜索“—有一点分治的意思在里面,(这个概念现在还不是很熟练,以后另外补上)
代码:

1 #include <iostream> 2 #include <cstring> 3 #include <queue> 4 #include <cstdio> 5 #include <cmath> 6 #include <map> 7 #include <algorithm> 8 typedef long long ll; 9 using namespace std; 10 int n1; 11 int sum=0; 12 13 int f(int m,int n) { 14 if(m==1||n==1) { 15 return 1; 16 } 17 if(m==n) { 18 //此时需要有一个做变动才行 19 return f(m,m-1)+1; 20 21 } 22 if(m<n) { 23 return f(m,m); 24 25 } 26 if(m>n) { 27 return f(m-n,n)+f(m,n-1); 28 } 29 30 31 } 32 33 34 35 int main() { 36 37 while(cin>>n1) { 38 cout<<f(n1,n1)<<" "; //最深不会超过n 39 } 40 41 42 43 44 return 0; 45 46 }
然后这是我没做出来的思路,如果不看题解,规定递归做,一开始会这样想,每次固定 a :
设 f(a,n)为分解 n 最终所得序列(这个序列整体作为一个状态,或者说自变量)n表示最终最多分解成n个1;
f(a,n)= { n:(n=a) || a-1 + f(n-a+1):(a=n-1,n-2,n-3,····,1),(要求,a >= f(n-a)中的任意元素)}
相当于a1 从 n一直试到 1 ,a1固定,拆n-a1,此时再固定a2,拆n-a1-a2,直到n-a1-a2-an,试到1 返回
然而 并。没。有。做出来。
目前反思:因为这里设置的边界条件是模糊的,a=1 并不清晰,因为这个条件不能保证求和的值 sum== n
做不出来的原因可能是,既想着模拟分割来做,又想着—搜索(第三步相当于dfs)来做,思路混乱,也不是很了解混用的流程,返回先决条件设置错误。,
那还做不出来怎么办,这个题显然可以用记忆化搜索查完,和刚刚那个想法类似,写了一个dfs的方式,
之前分割方式是否到底返回先决条件用和是否为 n 来判断
但是判断条件改为,令和为sum自动弹出当前stack,sum-stack,回到上一层,

1 #include <stdio.h> 2 #include <iostream> 3 # define MAXN 100 4 using namespace std; 5 int mark[256]; 6 int n; 7 int len=0; 8 int sum=0; 9 void DFS(int k, int pr) { 10 if(sum > n) { 11 return; 12 } else if(sum == n) { 13 len++; 14 15 } else { 16 for(int j = pr; j > 0; j--) { 17 mark[k] = j;//赋值是直接覆盖不用弹出 18 sum += j; 19 DFS(k+1,j); 20 sum -= j; 21 } 22 } 23 } 24 int main() { 25 while(cin>>n) { 26 sum=0; 27 len=0; 28 DFS(0,n); 29 cout<<len<<" "; 30 } 31 return 0; 32 }
至于bfs实现,个人理解,目前题目要求已经限制了宽度,检查需要的深度=1(不用递归到最后就可以了),宽度遍历不存在优势,也不需要剪枝
dp的思路——基于目前我对dp的理解程度
一般是利用二维数组,将搜索树的问题转化到二维数组——二元变量的关系上,一般难点再找关系和定义dp【i】【j】上,再找递推式
本题要求输入 n 的划分总数,容易想到,容易想到,大的划分可以分解成小的划分,总数也可以由此加上去,但是注意不能重复,
n 的m 划分数的求法:
1,如果分成的m 组 每个元素都不为零,那么相当于求 n-m 的m划分
2,如果有一个组的数字为0,那么就是求n的m-1划分
两种情况无交叉,步步递推能得到最终结果。
这种思路类似于分苹果,代码参照这位博主 : https://blog.csdn.net/liuke19950717/article/details/51017883
这里第二种不是很明白,回头再看看吧,想起来不容易,记忆化搜索更加实用。