Project Euler 78

Let p(n) represent the number of different ways in which n coins can be separated into piles. For example, five coins can be separated into piles in exactly seven different ways, so p(5)=7.

OOOOO
OOOO   O
OOO   OO
OOO   O   O
OO   OO   O
OO   O   O   O
O   O   O   O   O

Find the least value of n for which p(n) is divisible by one million.

    阅读题目不难发现p(n)就是对整数n进行分割的情况数。看到这样的题目首先脑海中浮现的就是递归法。

一、递归

    令f(sum,max)=对整数sum分割中最大值为max的情况,显然有p(n)=f(n,1)+f(n,2)+...+f(n,n)

    而根据定义,不难得到f(s,m)=f(s-m,1)+f(s-m,2)+...+f(s-m,s-m)

    再写出递归边界f(s,m)=1(s=m时),f(s,m)=0,(s<m),就可以算出p(n)

    这个方法好处是简洁易懂,好想。然而时间复杂度十分高,虽然我不太会计算递归的复杂度,不过起码是指数级数的时间了。用这种方法来虽然能得到p(n),却几乎不可能得到结果。

二、动态规划

    前面的递归算法效率实在太低,不得不对此进行优化。经过分析发现,我们重复计算了许多值,不妨以空间换时间,做一个三角形的数组,以储存f(sum,max),于是我们每次就只需要将前面的结果相加,就可以得到新的f(sum,max)。

    这种算法的复杂度是o(n^3),也十分不理想。(我在自家计算机上跑了20分钟才得到前10000的p(n)mod1000000的值)然而还没跑到答案。

三、母函数法

    这是我曾经在高中数学竞赛中接触的方法。。当年学了几乎没用过(捂脸)没想到在这种场合又有了用武之地。

    考虑函数G(x)=a0+a1*x+a2*x^2+...+an*x^n,则称G(x)是数列an的母函数。

    对数列an=p(n),设母函数为G1(x)=a0+a1*x+a2*x^2+...+an*x^n+...。

    考虑G2(x)=(1+x+x^2+...+x^n)(1+x^2+x^4+...+x^n+...)(1+x^3+...+x^n+..)....

    第一个括号表示 整数n的分割中1的个数,若取x^2,则1在n的分割中出现两次。

    分析G2每一项所表达的意义,可以发现G2(x)中x^k的系数恰恰就是p(k)。

    于是通过构造多项式相乘,我们就能够得到p(n)。时间复杂度同样是o(n^3),只不过可能相比第二种方法系数比较小,所以会快上一些,然而同样不能跑出结果。(跑出结果可能要八个小时左右?。。。不现实)

四、数论与五边形数定理

    在屡次尝试失败之后,查了一些资料。发现数学家Euler早已对这个问题给过比较完整的解答。Euler 的解答分三个部分:

    一、构造母函数(像上面那样)并化简成1/[(1-x)*(1-x^2)*(1-x^3)*...](化简可参见无穷级数)

    二、分析(1-x)*(1-x^2)*(1-x^3)*...(这个函数也叫欧拉函数)的系数。(结论:系数的通项为 k*(3k±1)/2)(这个东西证明起来十分复杂,自行百度五边形数定理)

    三、p(n)与欧拉函数的关系,通过分析n次项系数可得p(n)-p(n-1)-p(n-2)+p(n-5)+p(n-7)-p(n-12)-p(n-15)+…-…=0

    于是我们可以用前几项的p(n)来得到新的p(n),这是效率相当高的方法。时间复杂度o(n^3/2)。答案:55374。贴上代码:

#include <stdio.h> 
#include <malloc.h>
#include <string.h>

const int wu_size=10000;
const int max=1000000;

int main() {
	int *guangwu,*ans,flag=1;
	ans=(int *)malloc(max*sizeof(int));
	memset(ans,0,max*sizeof(int));
	guangwu=(int *)malloc(wu_size*sizeof(int));
	guangwu[0]=0;
	for (int i=1,k=1;i<wu_size;i+=2) {
		guangwu[i]=k*(3*k-1)/2;
		k++;
	}
	for (int i=2,k=1;i<wu_size;i+=2) {
		guangwu[i]=k*(3*k+1)/2;
		k++;
	}
	
	ans[0] = 1;
	for (int i=1;i<max;i++) {
		for (int j=1;guangwu[j]<=i;j++) {
			if (j%4==1||j%4==2) flag=1;
			else flag=-1;
			ans[i] += flag*ans[i-guangwu[j]];
			if (ans[i]>=1000000&&ans[i]>0) ans[i] %= 1000000;
			if (ans[i]<0) ans[i]+=1000000;
		}
		//if (i<=10) printf("%d\n",ans[i]); //for test
		if (ans[i]%1000000==0) {
			printf("%d\n",i);
			break;
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值