Problem 1
求将正整数N无序拆分成若干个不大于M的正整数的方案数
设
f[i][j]
f
[
i
]
[
j
]
表示
i
i
拆分成若干个不大于的正整数的方案数
考虑第
i
i
个数的大小是否为
f[i][j]=f[i−j][j]+f[i][j−1]
f
[
i
]
[
j
]
=
f
[
i
−
j
]
[
j
]
+
f
[
i
]
[
j
−
1
]
是为前者,否为后者
显然可以将空间优化成一维
时间复杂度为
O(nm)
O
(
n
m
)
,空间复杂度
O(n)
O
(
n
)
Problem 2
求将正整数N无序拆分成M个正整数的方案数
有序拆分即为组合问题,此略
Algorithm 1
由于其无序性,我们默认枚举拆分的正整数由小到大
设
f[i][j][k]
f
[
i
]
[
j
]
[
k
]
表示
i
i
拆分成个正整数,第
j
j
个为的方案数
枚举下一个数
l
l
,,则
f[i+l][j+1][l]+=f[i][j][k]
f
[
i
+
l
]
[
j
+
1
]
[
l
]
+
=
f
[
i
]
[
j
]
[
k
]
这样dp(由当前状态枚举更新未来状态)俗称刷表,刷表的好处是若当前状态不合法,此次无需花费转移的复杂度
但是算一下,总复杂度为
O(n3m)
O
(
n
3
m
)
Algorithm 2
考虑优化上述dp
我们换一种dp方式,枚举已有状态来更新当前状态,转移式为
f[i][j][k]+=f[i−k][j−1][l]
f
[
i
]
[
j
]
[
k
]
+
=
f
[
i
−
k
]
[
j
−
1
]
[
l
]
,
l≤k
l
≤
k
显然
f[i−k][j−1][l]
f
[
i
−
k
]
[
j
−
1
]
[
l
]
可以做前缀和,使得转移复杂度优化为
O(1)
O
(
1
)
那么总复杂度优化为
O(n2m)
O
(
n
2
m
)
,由此可见,这样dp的好处是可以做前缀和之类的优化
但是时间复杂度仍然不够优
Algorithm 3
我们多花费了一维状态来限制无序的条件,事实上可以换一种思路
设
f[i][j]
f
[
i
]
[
j
]
表示
i
i
拆分成个正整数的方案数
f[i][j]=f[i−1][j−1]+f[i−j][j]
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
−
1
]
+
f
[
i
−
j
]
[
j
]
在已选数集中增加一个1,方案数为
f[i−1][j−1]
f
[
i
−
1
]
[
j
−
1
]
将已选数集中每个数加1,方案数为
f[i−j][j]
f
[
i
−
j
]
[
j
]
我们甚至可以把空间优化成一维的
时间复杂度
O(nm)
O
(
n
m
)
,空间复杂度
O(n)
O
(
n
)
Algorithm 4
233其实Problem 2可以转化为Problem 1
如图,将数的大小形象成高度,从左到右依次是M个正整数
其中高度总和为N
首先每个数非零,于是将最底层用M的代价填满,总和还剩N-M
从下往上看,剩下每一行的数的个数单调递减
那么每一行相当于一个拆分,问题变为将N-M拆分成若干个不小于M的正整数
可以套用Problem 1的DP
Problem 3
求将正整数N无序拆分成若干个正整数的方案数
可以发现,Problem 3与Problem 2相比,去掉了“恰好M个加数”的限制。复杂度能不能更优秀呢?
这个问题等价于:
有N种物品,第i种重量为i,每种都有无限个,求装满容量为N的背包的方案数。
考虑平衡规划,对于
i≤n0.5
i
≤
n
0.5
,直接暴力做完全背包,
O(n1.5)
O
(
n
1.5
)
对于
i>n0.5
i
>
n
0.5
,所选物品数量不会超过
n0.5
n
0.5
,采用类似上面思想的dp。
设
f[i][j]
f
[
i
]
[
j
]
表示
i
i
个数和为的方案数,则转移为
f[i][j]=f[i−1][j−(n0.5+1)]+f[i][j−i]
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
−
(
n
0.5
+
1
)
]
+
f
[
i
]
[
j
−
i
]
显然也是
O(n1.5)
O
(
n
1.5
)
最后再把两部分合并即可。