把 M 个同样的苹果放在 N 个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?
注意:5、1、1 和 1、5、1 是同一种分法,即顺序无关。
/* 我的QQ:825580813(欢迎来一起讨论,刷题,PK)。 首先用递归的思想来思考这道题: 1,递归出口:当只有一个盘子或者 含有 0 个 或 1 个苹果的时候只有一种方法 2,当盘子数 n 大于苹果数 m 时,则必有 n - m 个空盘子,所以只需求 m 个盘子 放 m 个苹果时的方法数即可, 3,当盘子数 n 小于等于 苹果数 m 时,总方法数 = 当含有一个空盘子时的方法数 + 不含空盘子时的方法数。 原因:当在求只含有一个空盘子时的方法数时,已经包含了含有 2 ~ n - 1 个空盘子 的情况。 不含空盘子的计算:先将每个盘子装一个苹果,则问题变成了求 n 个盘子放 m - n 个苹果的方法数了。 其间,还可用记忆搜索优化(此处不再详讲) 然后用动态规划来优化代码: 新建一个动态规划表 dp;dp[i][j] 表示 i 个盘子放 j 个苹果的方法数。 则 当 i > j 时,dp[i][j] = dp[i - (i - j)][j] = dp[j][j](原因上面已经讲过) 当 i <= j 时,dp[i][j] = dp[i - 1][j] + dp[i][j - i]; 最后dp[n][m] 就是所求。 最后,可用空间压缩的办法进一步优化代码: 由于dp[i][j] 依赖其左边的 dp 与其的距离不太确定,所以就按行分割dp表啦*_*. (其实也还是有一定规律的,就是当 i < j 时左边依赖的 dp 与其的距离为 i , 只需用一个更小的数组来记录其左边与其相距为 i 的几个值即可, 这里代码实现就不写了,望君可写出)。 如果听了左神的课,这里的解释就很容易懂了,由于本人能力有限,只能讲到这里了。 */ #include <iostream> #include <algorithm> #include <cstdlib> #include <ctime> using namespace std; int process (int m, int n) { if( m == 0 || m == 1 || n == 1 ) { return 1; } if( n > m ) { return process (m, m); } else { return process (m, n - 1) + process (m - n, n); } } int putApple1 (int m, int n) { return process (m, n); } int** getdp1 (int m, int n) { if( m == 0 || m == 1 || n == 1 ) { return NULL; } int **dp = new int *[n + 1]; for( int i = 0; i <= n; i++ ) { dp[i] = new int[m + 1]; for( int j = 0; j <= m; j++ ) { dp[i][j] = 1; } } for( int i = 2; i <= n; ++i) { for( int j = 2; j <= m; ++j) { if( i > j ) { dp[i][j] = dp[j][j]; } else { dp[i][j] = dp[i - 1][j] + dp[i][j - i]; } } } return dp; } int putApple2 (int m, int n) { if( m == 0 || m == 1 || n == 1 ) { return 1; } int **dp = getdp1 (m, n); return dp[n][m]; } int* getdp2 (int m, int n) { if( m == 0 || m == 1 || n == 1 ) { return NULL; } int *dp = new int[m + 1]; for( int i = 0; i <= m; i++ ) { dp[i] = 1; } for( int i = 2; i <= n; ++i) { for( int j = 2; j <= m; ++j) { if( i <= j ) { dp[j] = dp[j] + dp[j - i]; } } } return dp; } int putApple3 (int m, int n) { if( m == 0 || m == 1 || n == 1 ) { return 1; } int *dp = getdp2 (m, n); return dp[m]; } int main () { srand ((unsigned)time (NULL)); for( int i = 0; i < 1000; i++ ) { int m = rand () % 20 + 1; int n = rand () % 20 + 1; cout << "case : " << i << endl; cout << "苹果数 : " << m << "\t盘子数:" << n << endl; int a1 = putApple1 (m, n); int a2 = putApple2 (m, n); int a3 = putApple3 (m, n); if( a1 != a2 ) { cout << "\n\n\t~~!!!Error!!!~~\n\n"; cout << "a1 = " << a1 << "\ta2 = " << a2 << "\ta3 = " << a3 << endl; break; } cout << "a1 = " << a1 << "\ta2 = " << a2 << "\ta3 = " << a3 << endl; cout << endl; } cout << "\n\nEND\n\n"; return 0; }
整数划分的一种:求将一个整数m至多划分成n个数有多少种情况 变形:求将一个整数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] import java.util.Scanner; public class Main { public static final int maxn = 25; public static void main(String[] args) { int[][] dp = new int[maxn][maxn]; for(int i=1;i<maxn;i++){ dp[i][1] = 1; dp[i][i] = 1; } for(int i=1;i<maxn;i++){ for(int j=2;j<maxn;j++){ if(i>j) dp[i][j] = dp[i-j][j] + dp[i-1][j-1]; else if(i == j) dp[i][j] = 1; else dp[i][j] = 0; } } Scanner sc = new Scanner(System.in); while(sc.hasNext()){ int m = sc.nextInt(); int n = sc.nextInt(); int res = 0; for(int i=1;i<=n;i++) res += dp[m][i]; System.out.println(res); } } }
#include<iostream> using namespace std; int main() { int m, n; while (cin>>m>>n) { int** dp = new int* [m + 1];//dp[i][j]表示i个苹果放在j个盘子中时,有多少种分法 for (int i = 1; i <= m; i++) { dp[i] = new int[n + 1]; //base case只有一个盘子时或只有一个水果时,只有一种分法 dp[i][1] = 1; for (int j = 1; j <= n; j++) dp[1][j] = 1; } for (int i = 1; i <= m; i++) { for (int j = 2; j <= n; j++) { if (i < j) //苹果比盘子少时,等于去掉一个盘子的分法 dp[i][j] = dp[i][j - 1]; //苹果和盘子一样多时,等于有空盘子去掉空盘子的分法+每个盘子一个的分法 else if (i == j) dp[i][j] = dp[i][j - 1] + 1; /*苹果比盘子多时,有两种情况 1、每个盘子都有苹果,那就等于每个盘子都拿掉一个苹果后的分法数 2、有空盘子(至少一个),就等于去掉空盘子后的分法*/ else dp[i][j] = dp[i - j][j] + dp[i][j - 1]; } } cout << dp[m][n] << endl; for (int i = 1; i <= m; i++) delete[] dp[i]; delete[] dp; } return 0; }
while True:#允许读入多组样例 try: def solution(m,n,k):#m个苹果放入n个盘子,每个盘子不得少于k #若n个盘子每个盘子放入k个恰好放完,则有1种解法 if k*n==m: return 1 #若剩下的m个苹果无法使得每个盘子的苹果都不少于k个,则无解 elif k*n>m: return 0 res=0 for i in range(k,m+1): for j in range(1,n+1): #若前j个盘子每个盘子都放入i个苹果,那么问题变为剩下m-i*j个苹果放入剩下n-j个盘子,每个盘子至少放i+1个,有几种放法 res+=solution(m-i*j,n-j,i+1) return res #读入数据 m,n=list(map(int,input().split(' '))) #输出结果 print(solution(m,n,0)) except EOFError: break
#include<stdio.h> #include<string.h> int dp[105][105][105]; int main(){ int N,M,i,j,T,k; //freopen("input.txt","r",stdin); while(scanf("%d%d",&M,&N)!=EOF){ memset(dp,0,sizeof(dp)); for(k=1;k<=N;k++) for(i=0;i<=M;i++) for(j=0;j<=i;j++) dp[k][i][j]=1; for(k=2;k<=N;k++) for(i=1;i<=M;i++) for(j=1;j<=M;j++){ dp[k][i][j]=dp[k][i-1][j]; if(j>=i) dp[k][i][j]+=dp[k-1][i][j-i]; } printf("%d\n",dp[N][M][M]); } }//把问题转化为:在 0...M 中 , 只取 N 个数 , 使得他们的和为M的情况有多少种 // 那么 dp[k][i][j]的意义就是 在0到i中 取k个数 使得和为j的情况数
两种方案 import java.util.Scanner; // write your code here public class Main{ public static void main(String[] args) { Scanner in = new Scanner(System.in); while (in.hasNext()) { int m = in.nextInt(); int n = in.nextInt(); int count = setApple(m, n); System.out.println(count); } } // m是苹果个数,n为盘子个数 public static int setApple(int m, int n) { // if (m <= 0 || n == 1) // return 1; // if(m < n) // return setApple(m, m); // 苹果数目小于盘子 // else // return setApple(m, n - 1) + setApple(m - n, n); // 两种情况:留一个空盘子不放+放n-1个盘子,剩下一个放m-n // dp[i][j]表示i个苹果,j个盘子的摆放策略 int maxn = 25; int[][] dp = new int[maxn][maxn]; for (int i = 0; i < maxn; i++){ dp[i][0] = 1; dp[i][1] = 1; } for (int j = 0; j < maxn; j++){ dp[0][j] = 1; dp[1][j] = 1; } for(int i = 2; i < maxn; i++){ for(int j = 2; j < maxn; j++){ if(i >= j) dp[i][j] = dp[i][j - 1] + dp[i - j][j]; else dp[i][j] = dp[i][i]; // 当苹果数目小于盘子时 } } return dp[m][n]; } }
整数划分问题 #include<iostream> #include<vector> #include<string> #include<algorithm> #include<functional> #include <map> #include <set> #include <unordered_set> #include <unordered_map> #include <exception> #include <iomanip> #include <memory> #include <sstream> using namespace std; int main(int argc, char** argv) { //freopen("in.txt", "r", stdin); int n,m; while (cin >> m >> n) { vector<vector<int>> dp(n + 1, vector<int>(m + 1,0)); dp[0][0] = 1; for (int i = 1; i <= n; ++i) { for (int j = 0; j <= m; ++j) { if (j >= i) dp[i][j] = dp[i - 1][j] + dp[i][j - i]; else dp[i][j] = dp[i - 1][j]; } } cout << dp[n][m] << endl; } return 0; }
import java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); while (sc.hasNext()) { int m = sc.nextInt(); int n = sc.nextInt(); System.out.println(fun(m, n)); } } public static int fun(int m, int n) { if(m == 0 || n == 1) return 1; if(n > m) return fun(m, m); else return fun(m, n - 1) + fun(m - n, n); } }
#include <iostream>usingnamespacestd;intdp(intm,intn){// 递归出口:有0个苹果 || 只有1个盘子if(m == 0 || n == 1)return1;if(n>m)// 盘子比较多,肯定有空盘子,去掉必空的盘子returndp(m, m);else// 苹果比较多:// 1:至少有一个空盘子,拿掉这个空盘子// 2:每个盘子都有苹果,各拿掉一个苹果(极限是最少的有1个苹果)returndp(m, n - 1) + dp(m - n, n);}intmain(){intm, n;while(cin >> m >> n)cout << dp(m, n) << endl;return0;}
#include <iostream> using namespace std; int dp(int m, int n) { // 递归出口:有0个苹果 || 只有1个盘子 if (m == 0 || n == 1) return 1; if (n>m) // 盘子比较多,肯定有空盘子,去掉必空的盘子 return dp(m, m); else // 苹果比较多: // 1:至少有一个空盘子,拿掉这个空盘子 // 2:每个盘子都有苹果,各拿掉一个苹果(极限是最少的有1个苹果) return dp(m, n - 1) + dp(m - n, n); } int main() { int m, n; while (cin >> m >> n) cout << dp(m, n) << endl; return 0; }
动态规划算法
#include <iostream>
using namespace std;
int main()
{
int m, n, i, j, dp[21][21];
while(cin >> m >> n)
{
for (i = 1; i <= m; i++) dp[i][1] = 1;
for (i = 1; i <= m; i++)
{
for (j = 2; j <= n; j++)
{
if (i < j) dp[i][j] = dp[i][j - 1];
else if(i == j) dp[i][j] = dp[i][j - 1] + 1;
else dp[i][j] = dp[i - j][j] + dp[i][j - 1];
}
}
cout << dp[m][n] << endl;
}
return 0;
}
动态规范
while True: try: m,n = list(map(int,input().split())) reuslts = [[0 for i in range(n + 1)] for j in range(m + 1)] #reuslts[i][j]表示j个盘子装i个苹果 for i in range(1,m+1): reuslts[i][1] = 1 for j in range(2,n+1): if i < j: #盘子比苹果多并不会增加分法 reuslts[i][j] = reuslts[i][j - 1] elif i == j: #盘子和苹果一样多,相当于多了一个全部盘子只摆一个,其他摆法起码有一个空盘 reuslts[i][j] = reuslts[i][j - 1] + 1 else: #盒子比苹果少,等于有一个空盘加上全部盘摆上一个苹果剩余的摆法 reuslts[i][j] = reuslts[i - j][j] + reuslts[i][j - 1] print(reuslts[m][n]) except Exception as message: print(message) break
#include <iostream> using namespace std; int dp[21][21]; int main(void) { int m, n; while (cin >> m >> n) { for (int i = 0; i <= n; ++i) { dp[0][i] = 1; } for (int i = 0; i <= m; ++i) { dp[i][1] = 1; } for (int i = 1; i <= m; ++i) { for (int j = 1; j <= n; ++j) { if (i < j) { dp[i][j] = dp[i][i]; } else { dp[i][j] = dp[i][j - 1] + dp[i - j][j]; } } } cout << dp[m][n] << endl; } return 0; }
#include <iostream> #include <vector> using namespace std; int main() { int m,n; while(cin>>m>>n){ vector<int>dp(m+1,0); dp[0]=1; for(int i=1;i<=n;i++){ for(int j=i;j<=m;j++){ dp[j]+=dp[j-i]; } } cout<<dp[m]<<endl; } }
#include <stdio.h> #include <string.h> #define MAX 20 long long int dp[MAX][MAX]; int main() { int n,m; while (scanf("%d %d",&m,&n)!=EOF) { //苹果如果是0,盘子如果1个分法都是1种,问题的边界 memset(dp, 0,sizeof(dp)); for(int i=0;i<=m;i++){ dp[i][1]=1; } for(int i=1;i<=n;i++){ dp[0][i]=1; dp[1][i]=1; } for(int i=2;i<=m;i++){ //i是苹果数,j是盘子数 for(int j=2;j<=n;j++){ if(i>=j){ dp[i][j]=dp[i][j-1]+dp[i-j][j]; }else{ dp[i][j]=dp[i][i]; //盘子大于苹果 } } } printf("%lld\n",dp[m][n]); } }
#include <iostream> #include<vector> using namespace std;提交观点 //回溯:得到和为sum的两个数的组合。子元素是0~n vector<int> path; int all_choices = 0; void backt(int panzi, int n, int star, int sum) { //元素可以重复用。的组合 if (sum == n && path.size() == panzi) { all_choices++; } else if (sum < n) { for (int i = star; i <= n; i++) { sum += i; path.push_back(i); backt(panzi, n, i, sum); sum -= i; path.pop_back(); } } } int main() { //组合问题。遍历数组 //当空盘子数量=0~N-1。分别求有几种方法 //当苹果多过盘子。空盘子=0~p-1,堆数=1~p。当盘子p多过苹果a。空盘子=p-a~p-1,堆数=1~a int dui = 0, p, a; while (cin >> a >> p) { all_choices=0; if (p > a) dui = a; else dui = p; for (int i = 1; i <= dui; i++) { backt(i, a, 1, 0); } cout << all_choices<<endl; } } // 64 位输出请用 printf("%lld")