projecteuler No.78 Coin partitions

原文题目链接:

http://projecteuler.net/problem=78

翻译题目链接:

http://pe.spiritzhang.com/index.php/2011-05-11-09-44-54/79-78

通过人数:8032


题目分析:

粗一看,像是一道很经典的动态规划问题。于是我设基本状态(m,n)为m个硬币分n堆放所可以放的种类数,以(m,n)=(m-1,n-1)+(m-n,n)为递推关系,写出了如下代码:

#include<stdio.h>

int m[21000]={0};
int p[21000][21000]={0};

int main()
{
	for (int i = 1; i < 10000; i++)
	{
		for (int j = 1; j <= i; j++)
		{
			if (i == j)
				p[i][j] = 1;
			else
			{
				p[i][j] = p[i - 1][j - 1];
				if (i >= 2 * j)
					p[i][j] += p[i-j][j];
			}
			p[i][j] = p[i][j] % 1000000;
		}
		for (int j=1;j<=i;j++)
		{
			m[i] += p[i][j];
			m[i] = m[i] % 1000000;
		}
		if (m[i] == 0)
			printf("%d %d\n",i,m[i]);
	}
	return 0;
}
于是非常悲惨的发现:在内存允许范围内,没有找到...

接下来我尝试了将超过内存范围的地方使用递归,未超出的地方从数组取值的解法,但非常悲惨也非常正常的发现递归部分非常慢...一天之内算不完...

于是我上网查了一下资料,注意到了以下一片博文:

Project Euler 78: Investigating the number of ways in which coins can be separated into piles.

在这其中提到了维基百科中的这个条目:Partition(number theory)

那里有如下公式:(其中p(n)表示n个硬币的分堆数即整数n的分拆数)

\sum_{n=0}^\infty p(n)x^n = \prod_{k=1}^\infty \left(\frac {1}{1-x^k} \right)..............................................(1)
上式将等号右边按等比级数分解之后其组合意义就显然了。上式的右边即为p(n)的母函数

而p(n)的母函数的倒数即为欧拉函数,其系数有五边形数定理

\prod _{​{n=1}}^{\infty }(1-x^{n})=\sum _{​{k=-\infty }}^{\infty }(-1)^{k}x^{​{k(3k-1)/2}}=\sum _{​{k=0}}^{\infty }(-1)^{k}x^{​{k(3k\pm 1)/2}}.

即:(等号右边的系数为广义五边形数

(1-x)(1-x^{2})(1-x^{3})\cdots =1-x-x^{2}+x^{5}+x^{7}-x^{​{12}}-x^{​{15}}+x^{​{22}}+x^{​{26}}+\cdots .(2)

将以上两式联立,有:

(1-x-x^{2}+x^{5}+x^{7}-x^{​{12}}-x^{​{15}}+x^{​{22}}+x^{​{26}}+\cdots )(1+p(1)x+p(2)x^{2}+p(3)x^{3}+\cdots )=1

进行系数比较,即可得到p(n)的递推公式:

p(n)=p(n-1)+p(n-2)-p(n-5)-p(n-7)+\cdots

其减去的值的序列为广义五边形数序列,满足:

p_{n}={\frac  {3n^{2}\pm n}{2}}

这下,有了比较好的公式,就可以做了~


解题过程(代码仅供参考,因为偷懒,代码风格什么的实在不好意思...):

一、生成所需的广义五边形数序列:

int a[1000];
for (int i=0;i<1000;i++)
{
	int n=(i+1)/2;
	if (i%2==0)
		a[i]=(3*n*n+n)/2;
	else
		a[i]=(3*n*n-n)/2;
}
二、计算整数分拆序列并求出所需值:

int n[100000]={0};
n[0]=1;
for (int i=1;i<100000;i++)
{
	for (int j=1;a[j]<=i;j++)
	{
		if (j%4==1||j%4==2)
			n[i]+=n[i-a[j]];
		else
			n[i]-=n[i-a[j]];
	}
	n[i] = n[i] % 1000000;
	if (n[i]==0)
		printf("%d\n",i);
}
打印出所求值:55374


感觉还是要好好学习数学...这道题主要花的时间都花在错误的尝试和看数学证明上了。



以上只是我做题时的解法。

如果有更好的解法、更好的思路,欢迎评论讨论~O(∩_∩)O~




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值