HIT ~ 1402~整数划分问题(动态规划 or 母函数)



概念

编辑
所谓整数划分,是指把一个正整数n写成为
其中,
   
为正整数,并且
   
   
为n的一个划分。
如果
   
中的最大值不超过m,即
   
,则称它属于n的一个m划分。 [1]  

求划分个数

编辑

分析

这里我们记n的m划分的个数为
   
例如,当n=4时,有5个划分,即
   
   
   
   
   
注意:
   
   
被认为是同一个划分。
根据n和m的关系,考虑一下几种情况:
(一)当
   
时,无论m的值为多少
   
,只有一种划分,即
   
(二)当
   
时,无论n的值为多少,只有一种划分,即n个1,
   
(三)当
   
时,根据划分中是否包含n,可以分为以下两种情况:
(1)划分中包含n的情况,只有一个,即
   
(2)划分中不包含n的情况,这时划分中最大的数字也一定比n小,即n的所有
   
划分。
因此
   
(四)当
   
时,由于划分中不可能出现负数,因此就相当于
   
(五)当
   
时,根据划分中是否包含最大值m,可以分为以下两种情况:
(1)划分中包含m的情况,即
   
,其中
   
的和为n-m,因此这种情况下为
   
(2)划分中不包含m的情况,则划分中所有值都比m小,即n的
   
划分,个数为
   
因此
   
综上所述:

递归代码:

#include<bits/stdc++.h>
using namespace std;  
int equation(int n,int m)
{
    if(n==1||m==1)
        return (1);
    else if(n<m)
        return equation(n,n);
    else if(n==m)
        return 1+equation(n,n-1);
    else
        return equation(n-m,m)+equation(n,m-1);
}
int main()
{
    int equation(int n,int m);
    int n,m;
    printf("Please input 'n'(0<n<100):");
    scanf("%d",&n);
    printf("Please input 'm'(0<m<=n):");
    scanf("%d",&m);
    printf("quantity:%d\n",equation(n,m));
}

以上内容来自百度百科

题目网址:整数划分问题

整数划分问题

  HIT - 1402 

整数划分是一个经典的问题。希望这道题会对你的组合数学的解题能力有所帮助。

Input

每组输入是两个整数n和k。(1 <= n <= 50, 1 <= k <= n)

Output

对于每组输入,请输出六行。

第一行: 将n划分成若干正整数之和的划分数。
第二行: 将n划分成k个正整数之和的划分数。
第三行: 将n划分成最大数不超过k的划分数。
第四行: 将n划分成若干奇正整数之和的划分数。
第五行: 将n划分成若干不同整数之和的划分数。
第六行: 打印一个空行。

Sample Input

5 2
Sample Output
7
2
3
3
3

Hint:

  1. 将5划分成若干正整数之和的划分为: 5, 4+1, 3+2, 3+1+1, 2+2+1, 2+1+1+1, 1+1+1+1+1
  2. 将5划分成2个正整数之和的划分为: 3+2, 4+1
  3. 将5划分成最大数不超过2的划分为: 1+1+1+1+1, 1+1+1+2, 1+2+2
  4. 将5划分成若干奇正整数之和的划分为: 5, 1+1+3, 1+1+1+1+1
  5. 将5划分成若干不同整数之和的划分为: 5, 1+4, 2+3

思路:

1.将n划分成若干正整数之和的划分数(一定要完全理解!!!)

dp[n][k]表示n这个数划分为最大值不超过k的划分数

初始化条件:n==1||k==1时,dp[n][k]=1,因为最大k=1的时候只有一种分法就是n个数字都为1,或者n=1了,也只有一种分法就是一个1

还有dp[0][1~n]=1,因为当n=k时,且划分中含有k的情况此时dp[n-k][k]应该为1而不是0,所以我们将dp[0][1~n]赋值为1

《1》当n>=k时,dp[n][k]由两种状态可以得到,即n的划分中有k和没k

        ①dp[n-k][k],意为n的划分中含有k

        ②dp[n][k-1],意为n的划分中没有k

此时dp[n][k]=dp[n-k][k]+dp[n][k-1]

《2》当n<k时,因为不存在负数所以dp[n][k]=dp[n][n];

2.将n划分成k个正整数之和的划分数(n个苹果分在k个盘子里面,盘子不允许为空,问有多少种分法)

dp[n][k]表示n这个数分为k个数字的划分数-------n个苹果分在k个盘子里

初始化条件:当k=1的时候,dp[n][k]=1,只有一个盘子了只有一种情况

《1》n>=k时,dp[n][k]由两种状态得到,即n的划分中有1和没1

        ①dp[n-1][k-1],意为n的划分中含有1--------有一个盘子只放一个苹果,以后就不考虑这个盘子了

        ②dp[n-k][k],意为n的划分中没有1--------每个盘子都放一个苹果

《2》n<k时,dp[n][]k]=0--------n小于k,n个苹果放k个盘子,盘子还不允许为空,所以这种情况为0 

3.将n划分为最大数不超过k的划分数(跟第一问基本一模一样,POJ-1664跟这个问题一样)

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

dp[n][k]表示n这个数划分为最大值不超过k的划分数

初始化条件:dp[0~n][1]的值为1,任何数划分为不超过1的数都只有一种情况,dp[0][奇数]=1(原因同第一个问题)

《1》k为奇数

①n>=k时,dp[n][k]由两种状态来,一dp[n-k][k],意为n的划分中含有k,二dp[n][k-1],意为n的划分中没有k

②n<k时,dp[n][k]=dp[n][n],因为不存在负数;

《2》k为偶数,dp[n][k]=dp[n][k-1]

5.将n划分为若干不同正整数之和的划分数

dp[n][k]表示n这个数划分为最大值不超过k且不同的划分数

初始化条件:n==1时,dp[n][k]=1,因为最n=1了,也只有一种分法就是一个1

还有dp[0][1~n]=1,因为当n=k时,且划分中含有k的情况此时dp[n-k][k]应该为1而不是0,所以我们将dp[0][1~n]赋值为1

《1》当n>=k时,dp[n][k]由两种状态可以得到,即n的划分中有k和没k

        ①dp[n-k][k-1],意为n的划分中含有k,则再划分k就不能再用了

        ②dp[n][k-1],意为n的划分中没有k

此时dp[n][k]=dp[n-k][k]+dp[n][k-1]

《2》当n<k时,因为不存在负数所以dp[n][k]=dp[n][n];

代码如下:

//#include <bits/stdc++.h>
#include<iostream>
#include<cstring>
using namespace std;  
const int maxn=55;
int n,k,dp[maxn][maxn];
int a()//将n划分成若干正整数之和的划分数。
{
	memset(dp,0,sizeof(dp)); dp[0][0]=1;
	for(int i=0;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(i>=j) dp[i][j]=dp[i-j][j]+dp[i][j-1];
			else dp[i][j]=dp[i][i];//i=0的时候这里会把dp[0][1~n]初始化为1  
								   //这样后面在递推的时候会直接把dp[1][1~n]和dp[1~n][1]的情况赋值为1 
		}
	}
	return dp[n][n];
}
int b()//将n划分成k个正整数之和的划分数。
{
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(j==1) dp[i][j]=1;//j=1的时候这里会把dp[1~n][1]初始化为1 
			else if(i>=j) dp[i][j]=dp[i-j][j]+dp[i-1][j-1];
		}
	}
	return dp[n][k];
}
int c()//将n划分为若干最大值不超过k的正整数之和      除了返回值和a()不一样其他都一样 
{
	memset(dp,0,sizeof(dp)); dp[0][0]=1;
	for(int i=0;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(i>=j) dp[i][j]=dp[i-j][j]+dp[i][j-1];
			else dp[i][j]=dp[i][i];//i=0的时候这里会把dp[0][1~n]初始化为1 
								   //这样在递推的时候会直接把dp[1][1~n]和dp[1~n][1]的情况赋值为1 
		}
	}
	return dp[n][k];
}
int d()// 将n划分成若干奇正整数之和的划分数。
{
	memset(dp,0,sizeof(dp));
	for(int i=0;i<=n;i++)//初始化dp数组 
	{
        dp[i][1]=1;
        if(i&1) dp[0][i]=1;
    }
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(j&1)
			{
				if(i>=j) 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];
		}
	}
	return dp[n][n];
}
int e()//将n划分成若干不同整数之和的划分数。
{
	memset(dp,0,sizeof(dp));dp[0][0]=1;
	for(int i=0;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(i>=j) dp[i][j]=dp[i-j][j-1]+dp[i][j-1];
			else dp[i][j]=dp[i][i];//i=0的时候这里会把dp[0][1~n]初始化为1
								   //这样在递推的时候会直接把dp[1][1~n]的情况赋值为1  
		}
	}
	return dp[n][n];
}
int main()  
{
	while(~scanf("%d%d",&n,&k))
	{
		printf("%d\n%d\n%d\n%d\n%d\n\n",a(),b(),c(),d(),e());
	}
    return 0;
}

母函数法:

问题:将n划分为若干个正整数的划分数

我们可以构建一个函数:

g(x)=(x0+x1+x2+x3+...+xn)(x0+x2+x4+x6+...)(x0+x3+x6+x9+...)...(x0+xn)

第i个括号$(1+x^i+x^{2i}+x^{3i} · ·)$选择的元素代表了数字i在我们最终的划分中出现的次数

我们不要管X是什么因为我们用不到他,我们只用这个函数的指数和系数;这个多项式展开后X^n次方的系数就是n的划分数。

对于这样一个式子X^3是如何得到的呢,从第一个括号中挑X^3,其他括号挑X^0,或者从第一个括号挑X^1,第二个括号挑X^2,其他括号挑X^0,再或者从第三个括号挑X^3,

所以X^3的系数为三,正好是X^3的划分数。


我们可以看一下4的划分为:①1+1+1+1,②1+1+2,③3+1,④2+2,⑤4共五种情况,(也正好是这个构造多项式的X^4的系数,大家可以手推算一下有助于理解)

①从第一个括号中挑X^4代表四个1,其他括号全挑X^0

②从第一个括号中挑X^2代表俩个1,第二个括号中挑X^2代表一个2,其他括号全挑X^0

③从第一个括号中挑X^1代表一个1,第三个括号中挑X^3代表一个三

④从第二个括号中挑X^4代表两个2,其他括号全挑X^0

⑤从第四个括号中挑X^4代表一个4,其他括号全挑X^0

在这个多项式中X^4也就是这五种方法得到的。


例题OpenJ_Bailian-4117     

网址简单的整数划分问题

题意:将n划分为若干个正整数的划分数

代码:

#include <bits/stdc++.h> 
using namespace std;
const int maxn=55;
int a[maxn],b[maxn],c[maxn];
void Poly()///多项式乘法 
{
	memset(c,0,sizeof(c));
	for(int i=0;i<maxn;i++)
	{
		for(int j=0;j<maxn-i;j++)
		{
			c[i+j]+=(a[i]*b[j]);
		}
	} 
}
int main()
{
	for(int i=0;i<maxn;i++) a[i]=1;// 第一个括号g(x,1)=x^0+x^1+x^2+x^3...
	for(int i=2;i<maxn;i++) 
	{
		memset(b,0,sizeof(b));
		for(int j=0;j<maxn;j+=i) b[j]=1;// 第i个括号g(x,j)=x^(0*j)+x^(1*j)+x^(2*j)+x^(3*j)...
		Poly();
		memcpy(a,c,sizeof(c));//相乘结果从c复制到a 
	}
	int n;
	while(cin>>n)
	{
		cout<<a[n]<<endl;
	}
	return 0;
} 

例题UESTC-624          

网址:整数划分

题意:将n划分为若干个不相同的正整数

生成函数:

g(x)=(x0+x1)(x0+x2)(x0+x3)...(x0+xn)

代码:

#include <bits/stdc++.h> 
using namespace std;
const int maxn=1005;
const int MOD=19901014;
int a[maxn],b[maxn],c[maxn];
void Poly(int k)///多项式乘法 
{
	memset(c,0,sizeof(c));
	for(int i=0;i<maxn;i++)
	{
		for(int j=0;j<maxn-i;j+=k)
		{
			c[i+j]+=(a[i]*b[j])%MOD;
		}
	} 
}
int main()
{
	memset(a,0,sizeof(a));a[0]=a[1]=1;//第一个括号 g(x,1)=x^0+x^1
	for(int i=2;i<maxn;i++) 
	{
		memset(b,0,sizeof(b));
		b[0]=b[i]=1;// 第二个括号 g(x,j)=x^(0*j)+x^(1*j)
		Poly(i);
		memcpy(a,c,sizeof(c));//相乘结果从c复制到a 
	}
	int T,n;scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);printf("%d\n",c[n]);
	}
	return 0;
} 

这里再附一份我看不懂的代码:

#include <bits/stdc++.h> 
using namespace std;
const int maxn=1005;
const int MOD=19901014;
int c1[maxn],c2[maxn];
int main()
{
	c1[0]=1;c1[1]=1;
    for(int i=2;i<maxn;i++)
	{  
        for(int j=0;j<maxn;j++)
		{  
            c2[j]+=c1[j]%MOD;  
            if(j+i<maxn)  c2[i+j]+=c1[j]%MOD;
        }
        for(int j=0;j<maxn;j++)
		{
            c1[j]=c2[j];
            c2[j]=0;
        }
    }
	int T,n;scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);printf("%d\n",c1[n]);
	}
	return 0;
} 


问题:将n划分为若干个奇正整数之和的划分数

生成函数:

g(x)=(x0+x1+x2+x3+...+xn)(x0+x3+x6+x9+...)(x0+x5+x10+x15+...)...
代码读者自己练习把。

该问题还有些变种,如要求整数连续等等;
本人学识浅薄,如有错误,望大家不吝赐教;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值