关于整数划分的讨论
整数划分
问一个整数有多少种划分为整数之和的组合。
例如:
4 = 2 + 1 + 1 , 4 = 2 + 2 4=2+1+1 ,4=2+2 4=2+1+1,4=2+2
4 = 3 + 1 4=3+1 4=3+1
4 = 4 4=4 4=4
对于这个问题需要使用动态规划来解决
首先定义dp数组
我们定义
d
p
[
n
]
[
m
]
dp[n][m]
dp[n][m]是数n的不超过m的划分组合数
例如:
d
p
[
4
]
[
2
]
=
3
dp[4][2]=3
dp[4][2]=3,其中包含
4 = 2 + 1 + 1 , 4 = 2 + 2 4=2+1+1,4=2+2 4=2+1+1,4=2+2
递推式推导:
1、
d
p
[
n
]
[
1
]
=
1
dp[n][1]=1
dp[n][1]=1,因为任何数的不大于一的划分只能是许多个一的组合。
2、
d
p
[
n
]
[
n
]
=
d
p
[
n
]
[
n
−
1
]
+
1
dp[n][n]=dp[n][n-1]+1
dp[n][n]=dp[n][n−1]+1,这个1是指这个数本身这种划分方法。
3、
d
p
[
n
]
[
m
]
=
d
p
[
n
]
[
n
]
(
n
<
m
)
dp[n][m]=dp[n][n](n<m)
dp[n][m]=dp[n][n](n<m)n的划分不会超过n,所以更大范围的划分并不会增加组合。
4、
d
p
[
n
]
[
m
]
=
d
p
[
n
]
[
m
−
1
]
+
d
p
[
n
−
m
]
[
m
]
(
n
>
m
)
dp[n][m]=dp[n][m-1]+dp[n-m][m](n>m)
dp[n][m]=dp[n][m−1]+dp[n−m][m](n>m)
证明:首先我们以4为例先看看具体的过程
可以看到等式成立,我们可将一个1看做一个积木那么4的划分有
我可以知道的是
d
p
[
4
]
[
3
]
dp[4][3]
dp[4][3]一定包含了
d
p
[
4
]
[
2
]
dp[4][2]
dp[4][2]的所有情况,那么要找递推式关键是如何表示
d
p
[
4
]
[
3
]
−
d
p
[
4
]
[
2
]
dp[4][3]-dp[4][2]
dp[4][3]−dp[4][2],我们可以发现二者之差是4划分中带有3的情况,如图1+3中的绿色部分,这部分有多少种划分起决于剩余积木的摆放方式,即剩余数的划分方式,当然这部分也不能大于3.
我将这一断言推广到整个整数域,对于
d
p
[
n
]
[
m
]
(
n
>
m
)
dp[n][m](n>m)
dp[n][m](n>m)一定包含
d
p
[
n
]
[
m
−
1
]
dp[n][m-1]
dp[n][m−1],
d
p
[
n
]
[
m
]
−
d
p
[
n
]
[
m
−
1
]
dp[n][m]-dp[n][m-1]
dp[n][m]−dp[n][m−1]一定是n有最大数为m的划分的情况组合数。
于是有
代码实现
对于给定n与m所求范围不大且所求数量很小时,我们可以通过递归仅计算与结果有关的dp值,来减少运算。
#include<iostream>
using namespace std;
int work(int n, int m) {
if (m == 1)return 1;
if (n == m)return work(n, m - 1) + 1;
else if (m > n)return work(n, n);
else {
return work(n, m - 1) + work(n - m, m);
}
}
int main() {
int n, m;
cin >> n >> m;
cout << work(n, m) << endl;
system("pause");
return 0;
}
对于n,m很大且需要多次求解一个范围内的划分数时,可以使用动态规划,来避免重复运算。
#include<iostream>
using namespace std;
const int MAXN = 1e3;
int dp[MAXN][MAXN];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (j == 1)dp[i][j] = 1;
else if (i == j) dp[i][j] = dp[i][j - 1] + 1;
else if (i < j)dp[i][j] = dp[i][i];
else dp[i][j] = dp[i][j - 1] + dp[i - j][j];
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cout << dp[i][j] << ' ';
}
cout << endl;
}
system("pause");
return 0;
}
整数的素数划分
问一个整数有多少种划分为素数之和的组合。
例如:
4 = 2 + 1 + 1 , 4 = 2 + 2 4=2+1+1 ,4=2+2 4=2+1+1,4=2+2
4 = 3 + 1 4=3+1 4=3+1
这个问题需要我们对递推式进行一下修改,并需要额外维护一个数组。我们要解决问题需要计算出一个对应关系
p
[
n
]
p[n]
p[n]为小于n且最接近n的素数。
我们可以用O(n)的时间复杂度求出p[n]数组来实现对应。如图
有这个数组我们就可以推导递推式了
1、
d
p
[
n
]
[
1
]
=
1
dp[n][1]=1
dp[n][1]=1
2、
d
p
[
n
]
[
m
]
=
d
p
[
n
]
[
p
[
m
]
]
(
m
为
合
数
)
dp[n][m]=dp[n][p[m]](m为合数)
dp[n][m]=dp[n][p[m]](m为合数),
p
[
m
]
p[m]
p[m]是不大于m的最大的素数,而素数划分只能分出素数所以实际上
m
−
p
[
m
]
m-p[m]
m−p[m]之间的数都是不能出现的。所以扩大的这个范围不会增加组合数。
3、
d
p
[
n
]
[
n
]
=
d
p
[
n
]
[
n
−
1
]
+
1
(
n
是
素
数
)
dp[n][n]=dp[n][n-1]+1(n是素数)
dp[n][n]=dp[n][n−1]+1(n是素数),如果n是合数可以代入上式。
4、
d
p
[
n
]
[
m
]
=
d
p
[
n
]
[
p
[
m
−
1
]
]
+
d
p
[
n
−
m
]
[
m
]
(
m
是
素
数
)
dp[n][m]=dp[n][p[m-1]]+dp[n-m][m](m是素数)
dp[n][m]=dp[n][p[m−1]]+dp[n−m][m](m是素数),如果m是合数,可以先代入3、
证明:证明过程与上一题的类似,关键是找到
d
p
[
n
]
[
m
]
−
d
p
[
n
]
[
p
[
m
−
1
]
]
dp[n][m]-dp[n][p[m-1]]
dp[n][m]−dp[n][p[m−1]],二者之差一定是有m数的划分的组合。
代码实现
#include<iostream>
using namespace std;
const int MAXN = 1e3;
int dp[MAXN][MAXN];
int p[MAXN];
bool flag[MAXN];
void work(int n) {
for (int i = 2; i <= n; i++) {
if (flag[i])continue;
else {
int c = i << 1;
while (c <= n) {
flag[c] = 1;
c += i;
}
}
}
int tmp = 0;
for (int i = 1; i <= n; i++) {
if (!flag[i]) tmp = i;
p[i] = tmp;
}
}
int main() {
int n, m;
cin >> n >> m;
work(n);
for (int i = 1; i <= n; i++)
cout << p[i] << ' ';
cout << endl;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
if (j == 1)dp[i][j] = 1;
else if (i == j&&!flag[i]) dp[i][j] = dp[i][j - 1] + 1;
else if (i < j)dp[i][j] = dp[i][i];
else if (flag[j])dp[i][j] = dp[i][p[j]];
else dp[i][j] = dp[i][p[j - 1]] + dp[i - j][j];
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cout << dp[i][j] << ' ';
}
cout << endl;
}
system("pause");
return 0;
}
整数划分(考虑顺序)
问一个整数有多少种划分为整数之和的排序数。
例如:
4 = 1 + 1 + 2 , 4 = 1 + 2 + 1 , 4 = 2 + 1 + 1 4=1+1+2,4=1+2+1,4=2+1+1 4=1+1+2,4=1+2+1,4=2+1+1
4 = 2 + 2 4=2+2 4=2+2
4 = 1 + 3 , 4 = 3 + 1 4=1+3,4=3+1 4=1+3,4=3+1
4 = 4 4=4 4=4
考虑顺序的整数划分也是一个动态规划问题,而且递推式要比不考虑顺序的问题简单。
先定义dp数组:
d
p
[
n
]
dp[n]
dp[n]为n的整数划分排序数
递推式:
d
p
[
n
]
=
d
p
[
n
−
1
]
+
d
p
[
n
−
2
]
+
⋯
+
d
p
[
1
]
+
1
dp[n]=dp[n-1]+dp[n-2]+\cdots+dp[1]+1
dp[n]=dp[n−1]+dp[n−2]+⋯+dp[1]+1
证明:4=1+三的划分,4=2+二的划分,4=3+一的划分,4=4
整数的素数划分(考虑顺序)
问一个整数有多少种划分为素数之和的排序数。
d
p
[
n
]
dp[n]
dp[n]为n的素数划分排序数
d
p
[
n
]
=
d
p
[
n
−
1
]
+
d
p
[
n
−
3
]
+
⋯
dp[n]=dp[n-1]+dp[n-3]+\cdots
dp[n]=dp[n−1]+dp[n−3]+⋯
与上一题不同的是这里只取
4=素数+剩余的划分