复杂整数划分问题(动态规划)

本文认为整数划分问题的关键在于dp[i][j]的理解上,透彻理解它的含义,能帮助我们轻松的解决问题。

(1)将数字i分成j个整数;
(2)数字i的划分中可能出现的最大数为j。

一、题目简述

描述
将正整数n 表示成一系列正整数之和,n=n1+n2+…+nk, 其中n1>=n2>=…>=nk>=1 ,k>=1 。
正整数n 的这种表示称为正整数n 的划分。

输入
标准的输入包含若干组测试数据。每组测试数据是一行输入数据,包括两个整数N 和 K。
(0 < N <= 50, 0 < K <= N)

输出
对于每组测试数据,输出以下三行数据:
第一行: N划分成K个正整数之和的划分数目
第二行: N划分成若干个不同正整数之和的划分数目
第三行: N划分成若干个奇正整数之和的划分数目

二、问题分析

关于整数划分的一系列问题,个人认为关键在dp[i][j]的理解上,它有两层含义

(1)将数字i分成j个整数;
(2)数字i的划分中可能出现的最大数为j。

后面所有的解题都是围绕这两种含义展开的,这也可以说是本文的核心思想吧。

另外,整数划分的万能边界条件,dp[0][0]=1。不想考虑复杂的边界条件,就用它吧,我将该边界条件与每个小问题推理出的边界条件进行了比对,发现计算结果并没有什么区别,可以放心大胆的用。

1. N划分成K个正整数之和的划分数目

注意此处的限定,N必须划分成K个正整数,不允许有0的存在,所以j<=i。从另一个角度讲,dp[i][j]划分出的数里最大的必须是j,于是:
(1)j=i,dp[i][j]=1,因为划分的最大数必须是i,就一种情况;
(2)j<i,dp[i][j]=dp[i-1][j-1]+dp[i-j][j],这里是按照划分中最小数是否为1推出的,当最小数为1时,首先从i中取出1,占用1部分,然后把i-1分为j-1部分,dp[i-1][j-1]。当最小数比1大时,先给每一个部分都放一个1进去,花费j*1,再将i-j分到这j个部分里,dp[i-j][j]。

2. N划分成若干个不同正整数之和的划分数目

将N分成若干部分,但是每一部分值都不同,考虑dp[i][j],此处,理解为数字i的划分里最大值为j,因为要考虑不同的数,这样理解显然更容易解题。
(1)j=i,dp[i][j]=1+dp[i][j-1],当最大数为i时有 1 种情况,这里并不强制最大数必须为i,所以当最大数不为i时有dp[i][j-1]种可能;
(2)j<i,dp[i][j]=dp[i-j][j-1]+dp[i][j-1],最大数为j时,先从i中取出j,再对i-j进行划分,由于不允许划分重复,最大数变为j-1,dp[i-j][j-1]。最大数不为j时,那么最大为j-1,dp[i][j-1];
(3)j>i,dp[i][j]=dp[i][i],这里应该很容易理解,最大数不可能超过它自身。

3.N划分成若干个奇正整数之和的划分数目

这个问题我认为是随着理解角度的不同,解题复杂性就差的多了,如果将dp[i][j]理解为整数i划分成j个奇数,那解起来就麻烦了,我也查阅了很多资料,这种方法是最容易找到的,我不再叙述;此处最好的理解应该是整数i的划分里最大的数是j

首先,我们要求将N划分为多个奇数,对于dp[i][j],j为偶数表示划分里最大的数为偶数,那么它实质上只能取得的最大数为j-1(奇数),所有j%2=0,dp[i][j]=dp[i][j-1],之后讨论j为奇数的情况。

(1)j=i,dp[i][j]=1+dp[i][j-1],当最大数为i时有 1 种情况,这里并不强制最大数必须为i,所以当最大数不为i时有dp[i][j-1]种可能;
(2)j<i,dp[i][j]=dp[i-j][j]+dp[i][j-1],最大数为j时,先从i中取出j,再对i-j进行划分,允许划分重复,最大数依然为j,dp[i-j][j]。最大数不为j时,那么最大为j-1,dp[i][j-1];
(3)j>i,dp[i][j]=dp[i][i],这里同上。

三、源代码

// 复杂整数划分
//

#include <iostream>
using namespace std;
#include <cstring>

int main()
{
	int N=0, K=0;//数N
	int dp1[51][51]; int dp2[51][51]; int dp3[51][51];
	while (cin >> N >> K)
	{
		//N分成K个正数,要注意这里不允许出现0,所以j<=i,i最多分成j个非0数
		//dp[i][j],可以理解为该划分中一定会出现 j 
		memset(dp1, 0, sizeof(dp1));
		dp1[0][0] = 1;
		for (int i = 1; i <= N;++i)
		{
			//dp1[i][1] = 1;
			for (int j = 1; j <= i; ++j)//这里的j不大于i,
			{				
				if (i == j)    //一定出现i,就一种
					dp1[i][j] = 1 ; 

				else           //划分中出现1 + 划分中都比1大
					dp1[i][j] = dp1[i - 1][j - 1] + dp1[i - j][j];
			}
		}
		cout << dp1[N][K] << endl;

		//N分成若干不相同的整数
		memset(dp2, 0, sizeof(dp2));
		dp2[0][0] = 1;
		for (int i = 1; i <= N; ++i)
		{ 
			for (int j = 1; j <=N; ++j)
			{
				//dp2[0][j] = 1;
				if (j == i)
					dp2[i][j] = dp2[i][j - 1] + 1;
				else if (j > i)
					dp2[i][j] = dp2[i][i];
				else           //划分中出现了j则下一次最大到j-1;
					           //划分中不出现j,下一次同样不允许出现j
					dp2[i][j] = dp2[i - j][j - 1] + dp2[i][j - 1];
			}
		}
		cout << dp2[N][N] << endl;

		//N分成若干个奇数
		memset(dp3, 0, sizeof(dp3));
		dp3[0][0] = 1;
		for (int i = 1; i <= N; i++)
		{
			//dp3[i][1] = i % 2 ? 0 : 1;
			//dp3[i][0] = 0;
			for (int j = 1; j <= N; j++)
			{
				if (j % 2 == 0)//此处理解为划分中含有j,如果j为偶数,显然直接到j-1
					dp3[i][j] = dp3[i][j - 1];

				else     //以下三个分支中都将dp3[i][j]理解为划分中含有j
				{
					if (i < j)	
						dp3[i][j] = dp3[i][i];
					else if (i == j)
						dp3[i][j] = dp3[i][j - 1] + 1;
					else
						dp3[i][j] = dp3[i - j][j] + dp3[i][j - 1];
				}
			}
		}
		cout << dp3[N][N] << endl;
	}
    return 0;
}

 
 
最后,附上我简单整数划分的代码,方便自己与大家查看。

#include <iostream>
#include <cstring>
using namespace std;

int main()
{
	int num;
	int dp[51][51];
	memset(dp, 0, sizeof(dp));
	while (cin>>num)
	{
		dp[0][0]=1;
		for(int i=1;i<=num;++i)
		{
			for(int j=1;j<=num;++j)
			{
				if(j>i)
					dp[i][j]=dp[i][i];
				else if(j==i)//划分的最大数是否为i
					dp[i][j]=dp[i][j-1]+1;
				else		//划分的最大数是否为j
					dp[i][j]=dp[i-j][j]+dp[i][j-1];	
			}
		}
		cout<<dp[num][num]<<endl;
	}
		
	return 0;
}


发布了13 篇原创文章 · 获赞 7 · 访问量 587

猜你喜欢

转载自blog.csdn.net/hejnhong/article/details/105211551