# -*- coding: utf-8 -*-
"""
Created on Thu Apr 4 09:19:06 2019
@author: Administrator
把m个同样的小球放在n个同样的盒子里,允许有的盒子空着不放,问共有多少种不同的分法?
其中5,1,1和1,5,1 是同一种分法。
算法分析:
回溯算法:有2种思路,一种是确保后面的盒子中球不比前面的少,
另一种是确保后面的盒子中球不比前面的多。
第2种思路递归深度较少,但代码复杂些,特别要注意第n个盒子能放球的数量范围。
基本上递归问题都可以转化为记忆化搜索,然后转换为动态规划问题。
回溯算法相当于穷举,不但可以获得组合的数量,还可以存储具体的解空间。
记忆化搜索和动态规划算法均采用了确保后面的盒子中球不比前面的多的思路:
当没有球或者只剩1个盒子时,分法为1,即b[k][m] = 1;
当球比盒子少,则只需m个盒子,即b[k][m] = ms(m, m);
当球不少于盒子数量时,分成有空盒子和每个盒子至少装1个球情形,
即b[k][m] = ms(k-1, m) + ms(k, m-k)
动态规划的计算模型与记忆化搜索搜索相似,其中动态规划进行了降维优化。
"""
#确保后面的盒子中球不比前面的少
def fun_1(n, m):
#k表示k号盒子,m表示共m个球
def dfs(k, m):
nonlocal s
if k == n - 1:#最后一个盒子
a[k] = m
s += 1
print(f'{s}:{a[:n]}')
else:#至少还有2个盒子
a[k] = a[k-1]
while a[k] * 2 <= m:
dfs(k+1, m-a[k])
a[k] += 1
s = 0
a = [0] * n
dfs(0, m)
return s
#确保后面的盒子中球不比前面的多
def fun_2(n, m):
#k表示k号盒子,m表示共m个球
def dfs(k, m):
nonlocal s
#没有球或者已到最后一个盒子
if m == 0 or k == n - 1:
a[k] = m
s += 1
print(f'{s}:{a[:n]}')
else:#至少还有2个盒子
#确保后面的盒子中球不比前面的多
a[k] = min(m, a[k-1])
minNum = m//(n-k) + int(m%(n-k) != 0)
while a[k] >= minNum:
dfs(k+1, m-a[k])
a[k] -= 1
s = 0
a = [0] * n
for i in range(m, m//n + int(m%n != 0) - 1, -1):
a[0] = i
dfs(1, m-a[0])
return s
#记忆化搜索
def fun_3(n, m):
#计算给定k个盒子装m个球的方案总数
def ms(k, m):
if b[k][m] != 0:
return b[k][m]
if m == 0 or k == 1:
b[k][m] = 1
elif m < k:#球比盒子少,则只需m个盒子
b[k][m] = ms(m, m)
else:#分成有空盒子和每个盒子至少装1个球情形
b[k][m] = ms(k-1, m) + ms(k, m-k)
return b[k][m]
#记录给定n个盒子装m个球的方案总数
b = [[0 for i in range(m+1)] for j in range(n+1)]
b[0][0] = 1
return ms(n, m)
#动态规划:使用二维数组
def fun_4(n, m):
#记录给定n个盒子装m个球的方案总数
b = [[0 for i in range(m+1)] for j in range(n+1)]
#0个球放到i个盒子里
for i in range(n+1):
b[i][0] = 1
#j个球放到1个盒子里
for j in range(m+1):
b[1][j] = 1
#计算给定i个盒子装j个球的方案总数
for i in range(2, n+1):
for j in range(1, i):
b[i][j] = b[j][j]
for j in range(i, m+1):
b[i][j] = b[i-1][j] + b[i][j-i]
return b[n][m]
#动态规划优化1:使用2个一维数组
def fun_5(n, m):
#j个球放到1个盒子里
pre = [1] * (m + 1)
cur = [0] * (m + 1)
cur[0] = 1
#计算给定i个盒子装j个球的方案总数
for i in range(2, n+1):
for j in range(1, i):
cur[j] = pre[j]
for j in range(i, m+1):
cur[j] = pre[j] + cur[j-i]
pre = cur[:]
return pre[m]
#动态规划优化2:使用1个一维数组
def fun_6(n, m):
#j个球放到1个盒子里
f = [1] * (m + 1)
#计算给定i个盒子装j个球的方案总数
for i in range(2, n+1):
for j in range(i, m+1):
f[j] += f[j-i]
return f[m]
n, m = 3, 7
print(fun_1(n, m))
print(fun_2(n, m))
print(fun_3(n, m))
print(fun_4(n, m))
print(fun_5(n, m))
print(fun_6(n, m))