HDU 1028 Ignatius and the Princess III (母函数 或 动态规划DP)

传送门HDU 1028



Sample Input
  
  
4 10 20

Sample Output
  
  
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;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值