把 M 個一樣的蘋果放在 N 個一樣的盤子裏,容許有的盤子空着不放,問共有多少種不一樣的分法?
注意:五、一、1 和 一、五、1 是同一種分法,即順序無關。java
輸入包含多組數據。
每組數據包含兩個正整數 m和n(1≤m, n≤20)。算法
對應每組數據,輸出一個整數k,表示有k種不一樣的分法。測試
7 3
8
放蘋果,後一個盤子不能比前一個盤子放的平果數多。能夠用動態規劃算法實現,可是存在子問題重疊,時間複雜度高。spa
設f(m,n)爲m個蘋果,n個盤子的放法數目,則先對n做討論,
* 當n>m:一定有n-m個盤子永遠空着,去掉它們對擺放蘋果方法數目不產生影響。即if(n>m)f(m,n)=f(m,m)
* 當n<=m:不一樣的放法能夠分紅兩類:
(a)有至少一個盤子空着,即至關於f(m,n)=f(m,n-1);
(b)全部盤子都有蘋果,至關於能夠從每一個盤子中拿掉一個蘋果,不影響不一樣放法的數目,即f(m,n)=f(m-n,n).而總的放蘋果的放法數目等於二者的和,即f(m,n)=f(m,n-1)+f(m-n,n)遞歸出口條件說明:當n=1時,全部蘋果都必須放在一個盤子裏,因此返回1;當沒有蘋果可放時,定義爲1种放法;遞歸的兩條路,第一條n會逐漸減小,終會到達出口n==1;第二條m會逐漸減小,由於n>m時,咱們會returnf(m,m) 因此終會到達出口m==0.
綜上遞推公式爲: .net
f(m,n)=⎧⎩⎨1f(m,m)f(m,n−1)+f(m−n,n)m=0orn=1n>m>0m≥n>1code
該問題能夠變形爲:求將一個整數m劃分紅n個數有多少種狀況,其公式爲: 遞歸
dp[m][n]={1dp[m−n][n]+dp[m−1][n−1]n=1n>1圖片
對於變形後的問題,存在兩種狀況:
(a) n份中不包含1的分法,爲保證每份都>=2,能夠先拿出n個1分到每一份,而後再把剩下的m-n分紅n份便可,分法有:dp[m-n][n]
(b) n份中至少有一份爲1的分法,能夠先那出一個1做爲單獨的1份,剩下的m-1再分紅n-1份便可,分法有:dp[m-1][n-1]
要求能夠放蘋果的數,m能夠被劃分爲1到k(k=min{n,m}),因此總的方置方法數有dp[m][1]+…+dp[m][k]
這種方式和解法二很是類似,只是思考的角度不同。 get
import java.util.Scanner; /** * Declaration: All Rights Reserved !!! */ public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // Scanner scanner = new Scanner(Main.class.getClassLoader().getResourceAsStream("data.txt")); while (scanner.hasNext()) { int m = scanner.nextInt(); int n = scanner.nextInt(); System.out.println(placeApple4(m, n)); } scanner.close(); } ///////////////////////////////////////////////////////////////////////////////////// // 【解法三】 ///////////////////////////////////////////////////////////////////////////////////// /** * 放蘋果 * 變形:求將一個整數m劃分紅n個數有多少種狀況 * dp[m][n] = dp[m-n][n] + dp[m-1][n-1]; 對於變形後的問題,存在兩種狀況: * 1. n 份中不包含 1 的分法,爲保證每份都 >= 2,能夠先拿出 n 個 1 分到每一份, * 而後再把剩下的 m- n 分紅 n 份便可,分法有: dp[m-n][n] * 2. n 份中至少有一份爲 1 的分法,能夠先那出一個 1 做爲單獨的1份,剩下的 m- 1 再分紅 n- 1 份便可, * 分法有:dp[m-1][n-1] * 3. 要求能夠放蘋果的數,m能夠被劃分爲1到k(k=min{n, m}),因此總的方置方法數有dp[m][1]+...+dp[m][k] * @param m 蘋果個數 * @param n 盤子個數 * @return 共的放法數目 */ /** * 【非遞歸實現】 * 放蘋果 * * @param m 蘋果個數 * @param n 盤子個數 * @return 共的放法數目 */ private static int placeApple4(int m, int n) { int row = m + 1; int col = n + 1; // 最多能夠放的盤子個數 int min = Math.min(m, n); int[][] dp = new int[row][col]; // 只有一個盤子時,則只有一種放法 for (int i = 1; i < row; i++) { dp[i][1] = 1; } for (int i = 1; i < row; i++) { for (int j = 2; j < col; j++) { if (i > j) { dp[i][j] = dp[i - j][j] + dp[i - 1][j - 1]; } else if (i == j) { dp[i][j] = 1; } } } int rst = 0; for (int i = 1; i <= min; i++) { rst += dp[m][i]; } return rst; } ///////////////////////////////////////////////////////////////////////////////////// // 【解法二】 ///////////////////////////////////////////////////////////////////////////////////// /** * 解題分析: * 設f(m,n) 爲m個蘋果,n個盤子的放法數目,則先對n做討論, * 當n>m:一定有n-m個盤子永遠空着,去掉它們對擺放蘋果方法數目不產生影響。即if(n>m) f(m,n) = f(m,m) * 當n<=m:不一樣的放法能夠分紅兩類: * 一、有至少一個盤子空着,即至關於f(m,n) = f(m,n-1); * 二、全部盤子都有蘋果,至關於能夠從每一個盤子中拿掉一個蘋果,不影響不一樣放法的數目,即f(m,n) = f(m-n,n). * 而總的放蘋果的放法數目等於二者的和,即 f(m,n) =f(m,n-1)+f(m-n,n) * 遞歸出口條件說明: * 當n=1時,全部蘋果都必須放在一個盤子裏,因此返回1; * 當沒有蘋果可放時,定義爲1种放法; * 遞歸的兩條路,第一條n會逐漸減小,終會到達出口n==1; * 第二條m會逐漸減小,由於n>m時,咱們會return f(m,m) 因此終會到達出口m==0. */ /** * 【非遞歸實現】 * 放蘋果 * * @param m 蘋果個數 * @param n 盤子個數 * @return 共的放法數目 */ private static int placeApple3(int m, int n) { int row = m + 1; int col = n + 1; int[][] dp = new int[row][col]; for (int i = 0; i < row; i++) { dp[i] = new int[n + 1]; } for (int i = 0; i < row; i++) { for (int j = 1; j < col; j++) { if (i == 0 || j == 1) { dp[i][j] = 1; continue; } if (j > i) { dp[i][j] = dp[i][i]; } else { dp[i][j] = dp[i][j - 1] + dp[i - j][j]; } } } return dp[m][n]; } /** * 【遞歸實現】 * 放蘋果 * * @param m 蘋果個數 * @param n 盤子個數 * @return 共的放法數目 */ private static int placeApple2(int m, int n) { //m個蘋果放在n個盤子中共有幾種方法 //由於咱們老是讓m>=n來求解的,因此m-n>=0,因此讓m=0時候結束,若是改成m=1, //則可能出現m-n=0的狀況從而不能獲得正確解 if (m == 0 || n == 1) { return 1; } if (n > m) { return placeApple2(m, m); } else { return placeApple2(m, n - 1) + placeApple2(m - n, n); } } ///////////////////////////////////////////////////////////////////////////////////// // 【解法一】下面的方法時間複雜度太高,發生了子問題重疊 ///////////////////////////////////////////////////////////////////////////////////// /** * 放蘋果 * * @param m 蘋果個數 * @param n 盤子個數 * @return 共的放法數目 */ private static int placeApple(int m, int n) { // 用於保存結果 int[] rst = {0}; // 第一個盤子數放的蘋果數 placeApple(m, n, m, rst); // 下面和上面一行實現一樣的效果 // for (int i = m; i >= 0; i--) { // placeApple(i, n - 1, m - i, rst); // } return rst[0]; } /** * 放蘋果,後一個盤子不能比前一個盤子放的平果數多 * * @param max 當前盤子最多能夠放多少個蘋果 * @param n 剩下要放的盤子數目 * @param left 剩下的蘋果數目 * @param rst 保存結果 */ private static void placeApple(int max, int n, int left, int[] rst) { // 放最後能夠放的盤子 if (n == 1) { // 還剩下left個,不能爲負數,能夠選擇的數目大於剩下的數目 if (max >= left && left >= 0) { rst[0]++; } } // 不是最後一個能夠 else if (n > 1) { // 當前盤子能夠放[0,max個] for (int i = max; i >= 0; i--) { placeApple(i, n - 1, left - i, rst); } } } }