整数划分

整数划分问题

给两个整数 \(n\)\(k\)


将 n 划分成 k 个正整数之和的划分数

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)\)的方法;

递归

(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\)以达到递归边界,从而解决问题。其递推表达式如下:

\[if(n==1||m==1) return 1;\\ if(n<m) return f(n,n);\\ if(n==m) return 1+f(n,m-1);\\ if(n>m) return f(n-m,m)+f(n,m-1);\\ \]

for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++) {
		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];
	}
		 

将 n 划分成最大数不超过 k 的划分数

跟1的情况是一样的,最后输出\(dp[n][k]\)即可


将 n 划分成若干奇正整数之和的划分数

这个呢,我们首先需要调整边界状态:当\(m=1\)时,\(f(n,m)=1\);当\(n=1\)\(m>1\)时,\(f(n,m)=0\)

其次,我们需要调整状态转换公式:

\(f(n-m,m)+f(n,m-1); (n>m)\) 应该更改为:\(f(n-m,m)+f(n,m-2); (n>m)\)

\(n=m\)时,考虑是否取\(m\)值,分为两种情况:

\(m\)值,那么就只有一个\(n\)

不取\(m\)值,那么转化为\(f(n,m-2)\)

所以$f(n,m)=1+f(n,m-2) $

memset(dp,0,sizeof(dp));
	for(int i=0;i<=n;i++){
		dp[i][1]=1;
		if(i&1)//判断i是否是奇数,这是一个位运算 
		dp[0][i]=1;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(j&1){
				if(j<=i){
					dp[i][j]=dp[i-j][j]+dp[i][j-1];
				}
				else{
					dp[i][j]=dp[i][i];
				}
			}
			else{
				dp[i][j]=dp[i][j-1];
			}
		}
	} //

将 n 划分成若干不同整数之和的划分数

只需要考虑当\(n>m\)时,考虑是否取\(m\)

\(m\)值,即{m, {x1,x2,...xi}}, 其中{x1,x2,... xi} 的和为\(n-m\),可能再次出现\(m\),因此是(\(n-m\))的\(m\)划分,因此这种划分个数为\(f(n-m, m-1)\); 因为m不能再取了

不取\(m\)值, 划分中不包含\(m\)的情况,则划分中所有值都比\(m\)小,即\(n\)的(\(m-1\))划分,个数为\(f(n,m-1)\);

所以\(n>m\)的情况,\(f(n,m)=f(n-m,m)+f(n,m-1)\)

终止条件 :

\(n=1\)\(m>=1\) 肯定是1

但是当\(n=1 ,m<1\)或者 \(n>1 m=1\)时显然是0

for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++) {
		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-1];
	}
		 

将n划分为k个数的划分数(一个划分中正好有k个数)

划分中是否包含1,包含的话就是\(dp[i-1][k-1]\)(把1拿出来,然后将剩下的分为k-1份),不包含的话就是\(dp[i-k][k]\)(把1分给k份,每一份都有一个1之后将剩余的分成k份,这样就可以保证每一份都不为1)

for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++) {
		if(j==1) dp[i][j]=1;
		else dp[i][j]=dp[i-1][j-1]+dp[i-j][j];
	}
		 

posted @ 2020-09-01 20:53  ke_xin  阅读(100)  评论(0编辑  收藏  举报