题的链接:问题 1531: [蓝桥杯][算法提高VIP]数的划分
重点: dfs判重:保存上一次值,下一次循环时要小于上一次值就行,即最后序列是一个递减序列;或者下一次循环大于上一次值,即最后序列是一个递增序列。
参考代码1.0: 比较笨,通过位数构造序列,最后判断是不是n的大小。。。超时代码!!!
#include <string>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
int n, pos, k;
int s[10010];
//当前位数 需要位数 存储上一次数的大小
//判重 1 1 2 和 2 1 1 可以用第三个参数来保证其序列永远是从大到小即2 1 1
void dfs(int a, int k, int b)
{
if(a == k)
{
int sum = 0;
//求和
for(int i = 0; i < k; i++) sum += s[i];
//和为n时输出并计数
if(sum == n)
{
for(int i = 0; i < k; i++)
{
if(i == 0) cout << s[i];
else cout << "+" << s[i];
}
cout << endl;
pos ++;
}
return;
}
for(int i = 1; i <= n && i <= b; i++)
{
s[a] = i;
dfs(a + 1, k, i);
}
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++) dfs(0, i, n);
cout << pos << endl;
return 0;
}
参考代码2.0: 用第一个参数存储当前值,减为0则说明构造成功一组,进行输出;(累减为0)
#include <string>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
int n, pos, k;
int s[10010];
//当前数 上次最大数(判重) 当前数组下标
void dfs1(int a, int k, int j)
{
if(a == 0)
{
for(int i = 0; i < j; i++)
{
if(i == 0) cout << s[i];
else cout << "+" << s[i];
}
cout << endl;
pos ++;
return;
}
for(int i = 1; i <= k && i <= a; i++)
{
s[j] = i;
dfs1(a - i, i, j + 1);
}
}
int main()
{
cin >> n;
dfs1(n, n, 0);
cout << pos << endl;
return 0;
}
参考代码3.0: 类似2.0,这个是(累加为n)
#include <string>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
int n, pos, k;
int s[10010];
void dfs2(int a, int k, int j)
{
if(a == n)
{
for(int i = 0; i < j; i++)
{
if(i == 0) cout << s[i];
else cout << "+" << s[i];
}
cout << endl;
pos ++;
return;
}
for(int i = 1; i <= n - a && i <= k; i++)
{
s[j] = i;
dfs2(a + i, i, j + 1);
}
}
int main()
{
cin >> n;
dfs2(0, n, 0);
cout << pos << endl;
return 0;
}
注意: 前面的搜索超时代码的dfs,所以应该避免走重复子问题,用到了标记,记忆化搜索;
参考代码4.0: vis[m][k]为把m分成k份的种类数,将每种情况都存储下来,下次dfs到他的时候就可以直接取用而不是继续去重复的浪费时间去递归。
- m < k 时,即盒子多,球少,没法构造成k位数,则直接返回0;
- 盒子为1时,无论球多少,只有一种情况,即{m};
- 盒子与球相等时,则只有一种情况,每个盒子只能放一个球,即{1,1,1,1,1…}!
- 否则就是球数大于盒子数分为两种情况:
- 有1的序列,即至少有一个1,即dfs(m - 1, k - 1),从m中拿一个球放到k中,再将m - 1个球放到剩下的k - 1个盒子里;
- 没有1的序列,即每个盒子里至少大于等于2个球,即dfs(m - k, k), 先从m 个球中取 k 个球放到 k 个盒子里,再将剩下的m - k 个球放到 k 个盒子里, 这样就能保证每个盒子至少两个球,若m - k 后的值 小于k 则相当于 dfs(m, k) = dfs(m - 1, k - 1) + dfs(m - j ,k),即最后变为 dfs(m, k) = dfs(m - 1, k - 1) + 0;
所以最终递推公式:dfs(m, k) = dfs(m - 1, k - 1) + dfs(m - j ,k)
#include <string>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
int n, sum, k;
int vis[1010][1010];
//将m个求放到k个盒子
int dfs(int m, int k)
{
if(vis[m][k]) return vis[m][k];
//球数小于盒子数无法填满盒子,返回0
if(m < k) return 0;
//盒子数为1和盒子数为球数只有一种情况
if(k == 1 || k == m) return 1;
//球数大于盒子数则递归
return vis[m][k] = dfs(m - k, k) + dfs(m - 1, k - 1);
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++) sum += dfs(n, i);
cout << sum << endl;
return 0;
}
参考代码5.0: 动态规划DP, 按位计算,最后累加。
若是不好想,也可以以这种方式推到公式:也可以作为4.0推论的另一种解释
我们用dp[i][j]来表示把i划分为j份的方法,直接想方程不好想,我假设已经划分好了i
i = a1 + a2 + … + aj
不妨把每一个a都减去1
i-j = (a1-1) + (a2-1) + a(aj-1)
这时分法与原来一样
dp[i][j] = dp[i - j][1] + dp[i - j][2] +…+ dp[i - j][j - 1] + dp[i - j][j]
dp[i - 1][j - 1] = dp[i - j][1] + dp[i - j][2]… + dp[i - j][j - 1]
由上面两式推出:
dp[i][j] = dp[i - 1][j - 1] + dp[i - j][j]
#include <string>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
int n, pos;
int dp[1010][1010];
int main()
{
cin >> n;
dp[0][0] = 1;
for(int i = 1; i <= n; i++)//数
for(int j = 1; j <= n && j <= i; j++)//盒子
dp[i][j] = dp[i - j][j] + dp[i - 1][j - 1];
for(int i = 1; i <= n; i++) pos += dp[n][i];
cout << pos << endl;
return 0;
}
参考代码6.0: 动态规划DP,dp[i][j]表示把 i 拆分成不超过 j 的方案数,和5.0不同,目前我没有看懂!!!。。。。
转移方程:dp[i][j] = dp[i][j - 1] + dp[i - j][j]
#include <string>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
int n, pos;
int dp[1010][1010];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) dp[i][0]=0;
for(int i=0;i<=n;i++) dp[0][i]=1;
//dp[i][j]表示把i拆分成不超过j的方案数
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
//dp[i][j]只有包含j和不包含j两种情况
//dp[i][j-1]不包含j,dp[i-j][j]包含若干个j
if(i>=j) dp[i][j]=dp[i][j-1]+dp[i-j][j];
else dp[i][j]=dp[i][i];
}
}
printf("%d",dp[n][n]);
return 0;
}