zoukankan      html  css  js  c++  java
  • 递归基础_整数划分类问题_ 多状态转移复杂递推

    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 }
    View Code

    然后这是我没做出来的思路,如果不看题解,规定递归做,一开始会这样想,每次固定 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 }
    View Code

    至于bfs实现,个人理解,目前题目要求已经限制了宽度,检查需要的深度=1(不用递归到最后就可以了),宽度遍历不存在优势,也不需要剪枝

    dp的思路——基于目前我对dp的理解程度

    般是利用二维数组,将搜索树的问题转化到二维数组——二元变量的关系上,一般难点再找关系和定义dp【i】【j】上,再找递推式

    本题要求输入 n 的划分总数,容易想到,容易想到,大的划分可以分解成小的划分,总数也可以由此加上去,但是注意不能重复,

     
    对于某个数,n,可划分呢为,最多n项最少1项,那么就把划分为i项的数写为dp【i】,观察发现,

    n 的m 划分数的求法:

    1,如果分成的m 组 每个元素都不为零,那么相当于求 n-m 的m划分

    2,如果有一个组的数字为0,那么就是求n的m-1划分

     两种情况无交叉,步步递推能得到最终结果。

    这种思路类似于分苹果,代码参照这位博主 : https://blog.csdn.net/liuke19950717/article/details/51017883

     
    还有另外一种,利用二维数组的dp方法:https://blog.csdn.net/qq_31975227/article/details/68191053

    这里第二种不是很明白,回头再看看吧,想起来不容易,记忆化搜索更加实用。

    老实一点,可爱多了
  • 相关阅读:
    第十七天——类与类之间的关系(一)
    Eclipse替代keil
    ssm_crud 测试mapper生成结果
    ssm_crud mybatis-generator逆向生成
    ssm_crud 搭建项目
    ssm_crud 目录篇
    mybatis SQL语句 打印
    Caused by: java.lang.ClassNotFoundException: javax.servlet.SessionCookieConfig
    Caused by: java.io.FileNotFoundException: class path resource [applicationContext.xml] cannot be ope
    Spring Security开发安全的REST服务 下载
  • 原文地址:https://www.cnblogs.com/KID-yln/p/12530936.html
Copyright © 2011-2022 走看看