传送门:HDU 1028
4 10 20
5 42 627
题目大意:
对于给定的数N,问N有多少种不同的拆分方式。如:4=4 , 4=3+1 , 4=2+2 ,4=2+1+1 ,4=1+1+1+1 ,共5种不同的拆分方式,注意 4=1+3和4=3+1是同一种。1<=N<=120.
前置技能:
1.拆分数: 整数拆分就是把正整数 分解成若干正整数的和。不同的拆分方法的总数叫做拆分数。
2.母函数:关于母函数的讲解可以参考我写的文章普母函数详解。
这个题大约有两种做法,一种是母函数,一种是动态规划,个人感觉母函数的思路要简单一点,就是套模板,但是代码较动态规划的要多一点。
母函数版思路:
N最大为120,则N最多可以由N个数组成,即N个1组成,最少可以由一个数N组成。则可以把题目转化为,现在有值为1~120的120个数,每个数最多有120个,问N可以由给出这些数的不同的组成方式。这样就转化为了求组合数的问题,就可以用普通型母函数来解决了。
具体实现:
值为 i 的数就对应着 x^i ,即v[i]=i;每个数值的数最少有0个,则s[i]=0;每个数值的数最多有120个,则e[i]=120;初始化完毕后直接调用母函数的模板,然后输出结果就好。
#include<stdio.h>
#include<string.h>
#define MAXN 122
int a[MAXN],b[MAXN]; //a存储最终结果,b存储中间结果
int s[MAXN],e[MAXN],v[MAXN];
//s为第i个数最少的个数,e为第i个数最多的个数,v为第i个数的数值
//直接调用母函数模板
void mu(int n)
{ //n为因子个数
int i,j,k;
memset(a,0,sizeof(a));
a[0]=1;
for(i=1;i<=n;i++)
{
memset(b,0,sizeof(b));
for(j=s[i];j<=e[i]&&j*v[i]<=MAXN;j++)
for(k=0;k+j*v[i]<=MAXN;k++)
b[k+j*v[i]]+=a[k];
memcpy(a,b,sizeof(b));
}
}
int main()
{
int i,n;
//先初始化s、e、v数组
memset(s,0,sizeof(s));
for(i=0;i<122;i++)
{
e[i]=120;
v[i]=i;
}
mu(120);
while(~scanf("%d",&n))
{ //a[i]存的是x^v[i]前的系数,即第i个数(数值为v[i]的数)的拆分数
printf("%d\n",a[n]);
}
return 0;
}
动态规划版思路:
我们用dp[n][k]表示用若干最大不超过k的数组成数字n的不同方法数。则可以得到动态转移方程:dp[n][k]=dp[n][k-1]+dp[n-k][k] 。
也就是如果组成n的若干数中没有用到数字k,只用了比k小的数,则方法有dp[n][k-1]种;如果组成n的若干数中用到了数字k,也就是最大数为k,则先从n中拿出一个值为k的数,就保证了一定有k,剩余值为n-k的数可以有不超过k的若干数组成,有dp[n-k][k]种。将两者相加即可。
边界条件:
dp[n][1]=1,dp[1][n]=1,dp[0][n]=1 ,注意当dp[n][m]中m>n时,dp[n][m]=dp[n][n]
#include<stdio.h>
#include<string.h>
int main()
{
int i,j,n;
int dp[122][122];
memset(dp,0,sizeof(dp));
for(i=1;i<=120;i++)
{
dp[i][1]=1;
dp[1][i]=1;
dp[0][i]=1;
}
for(i=2;i<=120;i++)
for(j=2;j<=120;j++)
if(j>i) dp[i][j]=dp[i][i];
else dp[i][j]=dp[i][j-1]+dp[i-j][j];
while(~scanf("%d",&n))
{
printf("%d\n",dp[n][n]);
}
return 0;
}