蓝桥杯 未名湖边的烦恼 递推 dp

问题描述
  每年冬天,北大未名湖上都是滑冰的好地方。北大体育组准备了许多冰鞋,可是人太多了,每天下午收工后,常常一双冰鞋都不剩。
  每天早上,租鞋窗口都会排起长龙,假设有还鞋的m个,有需要租鞋的n个。现在的问题是,这些人有多少种排法,可以避免出现体育组没有冰鞋可租的尴尬场面。(两个同样需求的人(比如都是租鞋或都是还鞋)交换位置是同一种排法)
输入格式
  两个整数,表示m和n
输出格式
  一个整数,表示队伍的排法的方案数。
样例输入
3 2
样例输出
5
数据规模和约定
m,n∈[0,18]
解法一:递归。f(m,n)函数用于返回还鞋人数为m,租鞋人数为n时的合法方案数。main函数里读入还鞋人数m,租鞋人数n,直接调用f(m,n)函数。

  由题意,需要保证,当前队伍内,任意前i个必有还鞋人数大于等于租鞋人数,因为“每天下午收工后,常常一双冰鞋都不剩”,所以当天队伍第一位一定是还鞋,然后第二位可能是还鞋也可能是租鞋,如果第二位是还鞋的话,和第一位是还鞋的情况相同,如果第二位是租鞋的话下一位只能是还鞋,下一位是还鞋的话,又和第一位是还鞋的情况相同。符合递归的的基本思想:复杂问题可以分解为相同的子问题。

  本题用递归实现比较简单,从后往前考虑,只考虑当前队伍最后一位应该放置还鞋还是租鞋。

  最后一位放置还鞋的话,总还鞋人数减一,考虑队伍最后一位除外的前面所有人合法的方案数,为f(m - 1, n);同理,最后一位放置租鞋的话,合法的方案数为f(m, n - 1);两种情况相加即为最后一位放还鞋或租鞋的总方案数。

  边界情况:当队伍里还鞋人数m小于租鞋人数n时,无论如何摆放,都不合法,返回0;当队伍里的租鞋人数为0时,因为“两个同样需求的人(比如都是租鞋或都是还鞋)交换位置是同一种排法”,所以合法方案数只有1种。

  

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 int f(int m, int n) {
 4     if (m < n) {
 5         return 0;
 6     } else if (n == 0) {
 7         return 1;
 8     } else {
 9         return f(m - 1, n) + f(m, n - 1);
10     }
11     
12 }
13 int main() {
14     int m, n;
15     cin >> m >> n;
16     cout << f(m, n) << endl;
17     return 0;
18 }

解法二:动态规划。从集合角度考虑DP问题参考自闫氏DP分析法https://www.bilibili.com/video/BV1X741127ZM?from=search&seid=9453608681194784228

先看一段错误代码

 1 //错误代码 
 2 #include <bits/stdc++.h>
 3 using namespace std;
 4 const int N = 20;
 5 int dp[N][N];
 6 int main() {
 7     int m, n;
 8     cin >> m >> n;
 9     for (int i = 1; i <= m; i++) { //初始化,所有租鞋人数为0时,合法的方案数为1 
10         dp[i][0] = 1;
11     }
12     for (int i = 1; i <= m; i++) {
13         for (int j = 1; j <= n; j++) { 
14             dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
15         }
16     }
17     cout << dp[m][n] << endl;
18     return 0;
19 }

正解为5,输出了6。

输出dp[i][j]里的值debug一下

 1 //错误代码 
 2 #include <bits/stdc++.h>
 3 using namespace std;
 4 const int N = 20;
 5 int dp[N][N];
 6 int main() {
 7     int m, n;
 8     cin >> m >> n;
 9     for (int i = 1; i <= m; i++) { //初始化,所有租鞋人数为0时,合法的方案数为1 
10         dp[i][0] = 1;
11     }
12     for (int i = 1; i <= m; i++) {
13         for (int j = 1; j <= n; j++) { 
14             dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
15         }
16     }
17     //输出dp[i][j]的内容 
18     for (int i = 1; i <= m; i++) {
19         for (int j = 1; j <= n; j++) {
20             cout << "i:" << i << " " << "j:" << j << " " << "dp[i,j]:" << dp[i][j] << endl;
21         }
22     } 
23     return 0;
24 }

输出结果为:

依次分析:

i=1,j=1时,一人还鞋,一人租鞋,合法方案数为1,没毛病

i=1,j=2时,一人还鞋,两人租鞋,合法方案数为1。发现错误,由上面分析,当还鞋人数小于租鞋人数时,此时没有合法方案。

更改后的代码

 1 //正确代码 
 2 #include <bits/stdc++.h>
 3 using namespace std;
 4 const int N = 20;
 5 int dp[N][N];
 6 int main() {
 7     int m, n;
 8     cin >> m >> n;
 9     for (int i = 1; i <= m; i++) {
10         dp[i][0] = 1;
11     }
12     for (int i = 1; i <= m; i++) {
13         for (int j = 1; j <= i; j++) { //此处更改为j <= i 
14             dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
15         }
16     }
17     //输出dp[i][j]的内容 
18     /*for (int i = 1; i <= m; i++) {
19         for (int j = 1; j <= i; j++) {
20             cout << "i:" << i << " " << "j:" << j << " " << "dp[i,j]:" << dp[i][j] << endl;
21         }
22     } */
23     cout << dp[m][n] << endl;
24     return 0;
25 }

更改后的代码输出dp[i][j]的值

 

 即要保证对于每个i,对应的j<=i,确保每个dp[i][j]里存的都是正确的值

 

原文地址:https://www.cnblogs.com/fx1998/p/12591341.html