写在前面
“这些事儿在熟练之后,也许就像喝口水一样平淡,但却能给初学者带来巨大的快乐,我一直觉得,能否始终保持如初学者般的热情、专注,决定了在做某件事时能走多远,能做多好。” 该系列文章由python编写,所刷题目共三个来源:之前没做出来的 ;Leetcode中等,困难难度题目; 周赛题目;某个专题的经典题目,所有代码已AC。每日1-3道,随缘剖析,希望风雨无阻,作为勉励自己坚持刷题的记录。
整数划分Ⅰ
因为题目中有不允许空份的要求,所以n一定大于等于k。
动态规划
这是排列组合的思路:将n个球放在k个盒子中,球和盒子没有区别,不允许空盒。将上述情况划分为两部分:
- 至少有1个盒子只有一个小球。此时
dp[n][k]=dp[n-1][k-1]
。 - 不存在1个盒子只有一个小球,即所有盒子都至少有一个小球。此时我们可以从每个盒子中都拿出1个盒子,则动态转移方程为
dp[n][k]=dp[n-k][k]
。
注意,在n==k的时候,只满足第一种情况,否则出现空盒。
n,k = tuple(input().split(" "))
n,k = eval(n),eval(k)
dp = [[0 for _ in range(k+1)] for _ in range(n+1)]
# 放置到1个盒子中只有1种
for _ in range(1, n+1):
dp[_][1] = 1
# 空盒的情况不存在,即0,i的情况应该是0种方案
# 1个球放置到大于自己盒子的情况不存在
for _ in range(2, k+1):
dp[1][_] = dp [0][_] = 0
for ni in range(2,n+1):
# k一定满足小于等于n
for ki in range(1,k+1):
dp[ni][ki] = dp[ni-1][ki-1]+dp[ni-ki][ki]
if k==ni:
dp[ni][ni] = dp[ni-1][ni-1]
print(dp[n][k])
深度遍历做法
import math
def dfs(tmp, s, cur):
global ans
if cur == k:
if s == n:
ans += 1
return
# 防止重复,每次枚举要比上一次大
# 剪枝,枚举到i*(k-cur)+s<=n即可
for _ in range(tmp, math.ceil((n-s+1)/(k-cur))):
dfs(_, s+_, cur+1)
dfs(1, 0, 0)
print(ans)
整数划分Ⅱ
动态规划
与上一题的区别在于,将n个球放在k个盒子中,球和盒子没有区别,允许空盒,k不知道。所以此时的动态规划方程为n个球,n为最大值,递推方程为:
第三个式子是将n拆成n只有一种情况,第四个式子是将n分成最大值为m的式子有两种情况:
- 含m,则划分此刻相当于去掉一个m后,按照m再次划分(允许放重复个数)
- 不含m,则划分中所有值都比m小,即n的(m-1)划分,个数为f(n,m-1)
整数划分Ⅲ:其它变体
- N划分成若干个不同正整数之和:只需要将含m的情况中,最大值去掉m即可,
dp[n][m]=dp[n][m-1]+dp[n-m][m-1]
- N划分成若干个奇/偶正整数之和:使用
f[i][j]
和g[i][j]
分别表示划分成j个奇数/偶数,此处按照奇数为例子给出动态转移方程。
- 对g而言,因为划分后每个都是偶数,所以有
g[i][j] = f[i-j][j]
,每个偶数减去1变成奇数,共j个 - 划分中包含1和不包含1(此时从每个数中拿出1来)的情况,
f[i][j] = f[i-1][j-1] + g[i-j][j]
其实这里也可以基于g求是否包含2的情况,只不过i和j两个变量需要都有递归方程递归到起始条件(0,1这样的边界)就可以。