整数划分(nyoj 571)

1、将n划分成若干正整数之和的划分数:

dp[i][j]为将i划分为不大于j的划分数

  (1) 当i<j 时,i不能划分为大于i的数,所以dp[i][j]=dp[i][i]

  (2) 当i=j 时,若划分中含有j只有一种情况,若划分中不含j相当于将i划分为不大于j-1的划分数。此时dp[i][j]=1+dp[i][j-1]

   (3) 当i>j 时,可以根据划分中是否含有j分为两种情况。若划分中含有j,划分方案数为dp[i-j][j];若划分数中不含j,相当于将i划分为不大于j-1的划分数,为dp[i][j-1]。所以当i>jdp[i][j]=dp[i-j][j]+dp[i][j-1]

dp[n][n]为所求。


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

dp[i][j]为将i划分为j个整数的划分数。

    (1) i<j为不可能出现的情况,dp[i][j]=0

  (2) 若i=j,有一种情况:i可以划分为i1之和,dp[i][j]=1

  (3) 若i>j,可以根据划分数中是否含有1分为两类:

       第一类: n 份中不包含 1 的分法,为保证每份都 >= 2,可以先拿出 k 个 1 分
     到每一份,然后再把剩下的 n- k 分成 k 份即可,分法有: dp[n-k][k]
       第二类: n 份中至少有一份为 1 的分法,可以先那出一个 1 作为单独的1份,剩
     下的 n- 1 再分成 k- 1 份即可,分法有:dp[n-1][k-1]

dp[n][k]为所求。


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

通过1的方法 dp[n][k]为所求。


4、将n划分成若干个奇正整数之和的划分数:

设dp[i][j] 是将i划分成不大于j 的奇正整数的划分数。

    (1)当i < j 的时候,i  不能划分成大于i的数且只能划分为奇数,如果j是奇数,dp[i][j]=dp[i][i],如果j是偶数,dp[i][j]=dp[i][i - 1]

    (2)当i = j 的时候,若划分中含有j只有一种情况,若划分中不含j相当于将i划分为不大于j - 2的划分数, 因为偶数不能取,所以是j - 2。此时dp[i][j]=1+dp[i][j - 2]。

   (3)当i < j 时,可以根据划分中是否含有j分为两种情况。若划分中含有j,相当于把i - j划分为不大于j的划分数,为dp[i-j][j];若划分中不含j,相当于将i划分为不大于j - 2的划分数,为dp[i][j - 2]。所以dp[i][j]=dp[i-j][j]+dp[i][j-2]

dp[n][n]或者dp[n][n - 1]为所求。


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

dp[i][j]为将i划分为不超过j的不同整数的划分数。

  (1) 当i<j时,i不能划分为大于i的数,所以dp[i][j]=dp[i][i]

  (2) 当i=j时,若划分中含有j只有一种情况,若划分中不含j相当于将i划分为不大于j-1的划分数。此时dp[i][j]=1+dp[i][j-1]

  (3) 当i>j时,可以根据划分中是否含有j分为两种情况。若划分中含有j,则其余的划分中最大只能是j-1(与1不同的地方!!),方案数为dp[i-j][j-1];若划分中不含j,相当于将i划分为不大于j-1的划分数,为dp[i][j-1]。所以当i>jdp[i][j]=dp[i-j][j-1]+dp[i][j-1]

dp[n][n]为所求。

 
#include <stdio.h>

int dp1[100][100];//将n划分成若干正整数之和的划分数or将n划分成最大数不超过k的划分数
int dp2[100][100];//将n划分成k个正整数之和的划分数
int dp4[100][100];//将n划分成若干个奇正整数之和的划分数
int dp5[100][100];//将n划分成若干不同整数之和的划分数

void one()
{//n划分成若干正整数之和的划分数or将n划分成最大数不超过k的划分数 
将n划分成若干不同整数之和的划分数
	int i, j;
	for(i = 1; i < 100; i++)
	{//i划分成不大于1的划分数和1划分成不大于i的划分数为1 
		dp1[i][1] = dp1[1][i] = 1;
		dp5[1][i] = dp5[1][i] = 1;
	}
	for(i = 2; i < 100; i++)
	{
		for(j = 2; j < 100; j++)
		{
			if(i == j)
			{
				dp1[i][j] = dp1[i][j - 1] + 1;
				dp5[i][j] = dp5[i][j - 1] + 1;
			}
			else if(i < j)
			{
				dp1[i][j] = dp1[i][i];
				dp5[i][j] = dp5[i][i];
			}
			else
			{
				dp1[i][j] = dp1[i][j - 1] + dp1[i - j][j];
				dp5[i][j] = dp5[i][j - 1] + dp5[i - j][j - 1]; 
			}
		}
	}
}

void two()
{//将n划分成k个正整数之和的划分数
	int i, j;
	for(i = 0; i < 100; i++)
		dp2[i][i] = 1;//将i划分成i个整数的划分数只有一种 
	for(i = 2; i < 100; i++)
	{
		for(j = 1; j < i; j++)
			dp2[i][j] = dp2[i - j][j] + dp2[i - 1][j - 1];
	}
}

void three()
{//将n划分成若干个奇正整数之和的划分数
	int i, j;
	for(i = 0; i < 100; i ++)
	//将i划分为不大于1的正奇数的划分数和将1划分成不大于i的正奇数的划分数为1 
		dp4[i][1] = dp4[1][i] = 1;
		
	for(i = 1; i < 100; i++)
	{
		for(j = 3; j < 100; j += 2)
		{//j += 2
			if(i < j)
			{
				if(i % 2)
					dp4[i][j] = dp4[i][i];
				else
					dp4[i][j] = dp4[i][i - 1];
			}
			else//i == j和i > j的情况合并 
				dp4[i][j] = dp4[i - j][j] + dp4[i][j - 2];
		}
	}
}

int main (void)
{
	int n, k;
	one();
	two();
	three();
	while(scanf("%d %d", &n, &k) != EOF)
	{
		printf("%d\n", dp1[n][n]);
		printf("%d\n", dp2[n][k]);
		printf("%d\n", dp1[n][k]);
		if(n % 2)//注意判断 
			printf("%d\n", dp4[n][n]);
		else
			printf("%d\n", dp4[n][n - 1]);
		printf("%d\n\n", dp5[n][n]);
	}
	return 0;
}        

在网上还看到了将正整数划分成连续的正整数之和:

如15可以划分成4种连续整数相加的形式:
15
7 8
4 5 6
1 2 3 4 5
  

有些数可以写成连续N(>1)个自然数之和,比如14=2+3+4+5;有些不能,比如8.那么如何判断一个数是否可以写成连续N个自然数之和呢?一个数M若可以写成以a开头的连续n个自然数之和,则M=a+(a+1)+(a+2)+…+(a+n-1)=n*a+n*(n-1)/2,要求a不等于0,否则就是以a+1开头的连续n-1个整数了。

    现在考虑一般的形式,设n为被划分的正整数,x为划分后最小的整数,i为划分的正整数的个数。
有上述可知(i * x + i * (i - 1) / 2) = n, 满足条件的划分就是使x为正整数的所有情况。
如上例,当i = 1时,即划分成一个正整数时,x = 15,

当i = 2时, x = 7。
当i = 3时,x = 4, 当i = 4时,4/9,不是正整数,因此,15不可能划分成4个正整数相加。
当i = 5时,x = 1。

    这里还有一个问题,这个i的最大值是多少?不过有一点可以肯定,它一定比n小。我们可以做一个假设,
假设n可以拆成最小值为1的划分,如上例中的1 2 3 4 5。这是n的最大数目的划分。如果不满足这个假设,
那么 i 一定比这个划分中的正整数个数小。因此可以得到这样一个公式i * (i - 1) / 2 <= n,即当i满足
这个公式时n才可能被划分。

代码如下:

void split(int n) {
    int i, j, te, x, xlen;
    for (i = 1, xlen = 0; (te = i * (i - 1) / 2) < n; i++) {
        x = n - te;
        if (x % i == 0) {
            x /= i;
            printf("%d", x);
            for (j = 1; j < i; j++) {
                printf("%d ", x + j);
            }
            printf("\n");
            xlen++;
        }
    }
    printf("%d\n", xlen);
}

参考: 点击打开链接

 点击打开链接


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值