11088 整数划分的扩展问题(必做)
时间限制:1000MS 内存限制:65535K
提交次数:0 通过次数:0
题型: 编程题 语言: C++;C;VC;JAVA
Description
下面有整数划分问题扩展出的多个题例: (1)正整数n划分为若干正整数之和,最大加数不超过m的划分数 (2)正整数n划分为不超过m个正整数之和的划分数 (3)正整数n划分为若干正奇整数之和的划分数 (4)正整数n划分为互不相同正整数之和的划分数 约定: 整数划分无顺序,比如对7划分,认为2 2 3和3 2 2和2 3 2为同一种划分。
输入格式
两个数n和m,中间空格相连。n和m都不超过100。 如输入7 3 则:最大加数不超过3的划分为:(3 3 1)(3 2 2)(3 2 1 1)(3 1 1 1 1)(2 2 2 1)(2 2 1 1 1)(2 1 1 1 1 1)(1 1 1 1 1 1 1),共8种。 不超过3个正整数的划分为:(7)(6 1)(5 2)(5 1 1)(4 3)(4 2 1)(3 3 1)(3 2 2),共8种。 若干正奇数的划分为:(7)(5 1 1)(3 3 1)(3 1 1 1 1)(1 1 1 1 1 1 1),共5种。 互不相同正整数的划分为:(7)(6 1)(5 2)(4 3)(4 2 1),共5种。
输出格式
四个数,中间空格相连,分别为上面四个题例的结果。其中m参数只和题例(1)和(2)有关,与(3)(4)无关。
输入样例
7 3
输出样例
8 8 5 5
提示
整数划分问题是算法中的一个经典命题之一,有关这个问题的讲述在讲解到递归时基本都将涉及。所谓整数划分,是指把一个正整数n写成如下形式:
n=m1+m2+...+mi; (其中mi为正整数,并且1 <= mi <= n),则{m1,m2,...,mi}为n的一个划分。
如果{m1,m2,...,mi}中的最大值不超过m,即max(m1,m2,...,mi)<=m,则称它属于n的一个m划分。这里我们记n的m划分的个数为f(n,m);
例如但n=4时,他有5个划分,{4},{3,1},{2,2},{2,1,1},{1,1,1,1};
注意4=1+3 和 4=3+1被认为是同一个划分。
该问题是求出n的所有划分个数,即f(n, n)。下面我们考虑求f(n,m)的方法;
---------------------------------------------------------------------
(一)
---------------------------------------------------------------------
根据n和m的关系,考虑以下几种情况:
(1)当 n = 1 时,不论m的值为多少(m > 0 ),只有一种划分即 { 1 };
(2) 当 m = 1 时,不论n的值为多少,只有一种划分即 n 个 1,{ 1, 1, 1, ..., 1 };
(3) 当 n = m 时,根据划分中是否包含 n,可以分为两种情况:
(a). 划分中包含n的情况,只有一个即 { n };
(b). 划分中不包含n的情况,这时划分中最大的数字也一定比 n 小,即 n 的所有 ( n - 1 ) 划分。
因此 f(n, n) = 1 + f(n, n-1);
(4) 当 n < m 时,由于划分中不可能出现负数,因此就相当于 f(n, n);
(5) 但 n > m 时,根据划分中是否包含最大值 m,可以分为两种情况:
(a). 划分中包含 m 的情况,即 { m, { x1, x2, ..., xi } }, 其中 { x1, x2, ..., xi } 的和为 n - m,可能再次出现 m,因此是(n - m)的 m 划分,因此这种划分
个数为 f(n-m, m);
(b). 划分中不包含 m 的情况,则划分中所有值都比 m 小,即 n 的 ( m - 1 ) 划分,个数为 f(n, m - 1);
因此 f(n, m) = f(n - m, m) + f(n, m - 1);
综合以上情况,我们可以看出,上面的结论具有递归定义特征,其中(1)和(2)属于回归条件,(3)和(4)属于特殊情况,将会转换为情况(5)。而情况(5)为通用情况,属于递推的方法,其本质主要是通过减小m以达到回归条件,从而解决问题。其递推表达式如下:
f(n, m) = 1; ( n = 1 or m = 1 )
f(n, n); ( n < m )
1+ f(n, m - 1); ( n = m )
f(n - m, m) + f(n, m - 1); ( n > m )
---------------------------------------------------------------------
(二)
---------------------------------------------------------------------
思想: 转化为子集和问题: 集合{1,2,…,n},挑选若干正整数,使之和为n,这也是一个背包装物品问题。 #include
using namespace std;
int m,n;
int f1(int n,int m)
{
if(n<1||m<1) //不为负数
return 0;
if(m==1||n==1) //1的m划分 或 n的1划分 都是 1
return 1;
if(n==m) // 含n是 1 不含的是 n 的 n-1 划分
return 1+f1(n,n-1);
if(n
return f1(n,n);
// 含 m 的是 n-m 的 m 划分 因为 n-m中可能再次出现m
// 不含 m 的是 n 的 m-1 划分
return f1(n-m,m)+f1(n,m-1);
}
int f2(int n,int m)
{
if(m==0) //F(I,0)=1(I>0)
return 1;
if(m<0) //F(I,k)=0(k<0)
return 0;
if(n==1&&m==1) //F(1,1)=1
return 1;
if(n==1&&m>1) //F(1,k)=0(k>1)
return 0;
//设F(I,J): 表示挑选集合前I个,使之和为J的方式数。
// F(I,J) = F(I-1,J) + F(I-1,J-I), 第I个不挑的方式数 + 挑第I个的方式数
return f2(n-1,m)+f2(n-1,m-n);
}
int main()
{
cin >> n >> m;
cout << f1(n,m) <<" "<< f1(n,m) << " ";
cout << f2(n,n) <<" "<< f2(n,n) << endl; //m参数与(3)(4)无关
return 0;
}