http://acm.hdu.edu.cn/showproblem.php?pid=4345
问题即是求相加小于等于n的质数的最小公倍数有几个。推荐网站:http://oeis.org/,只需将前面几个数字的答案输入,即可得出序列,打表即过(每想到这里,我就想笑)。
解题报告说用记忆化搜索,没想明白,估计就是跟dp差不多,用前面的去推后面的。
今晨参悟http://blog.youkuaiyun.com/cyberzhg/article/details/7840340代码,有感于心,记录一下思路。
首先,考虑n的答案是不是可以由n - 1得来。答案是肯定的。这么设计:
用ans[i]表示状态i的答案数;
用a[i][j]记录表示将相加等于i的质数的最小公倍数的方案数,且最小质数的序号为j;
状态转移方程:
因为素数px跟谁都互质,所以呢,如下
a[i][j] += a[i – p1][大于j];(因为j表示的是最小的序号,所以肯定需要大于序号j的素数去组成i – p1)
a[i][j] += a[i – p1^2][大于j];
a[i][j] += a[i – p1^3][大于j];
……
i - p1 ^ x >= 0
a[i][j] += a[i – p2][大于j];
a[i][j] += a[i –p2 ^ 2][大于j];
a[i][j] += a[i – p2 ^ 3][大于j];
…… ……
pn ^ x < i就行
当pn^x == i时,只有一个最大公倍数方案并且前面不曾出现过,a[i][j] ++;
这个其实就是转换了求解的方法
因为包含最小素因数为prime[j]的部分已经被考虑在tmp里面了,剩下的i-tmp里面不可以再有prime[j]作为素因数,不然的话情况就是接下来tmp*prime[j]那种了。
其实就是 分类讨论 ,用数学的思想去想!

当我们得出所有的a[i][j]之后,对于每个i将值累加到a[i][0]里面。此时,a[i][0]存的就是求相加等于n的质数的最小公倍数有几个。
然而,问题是求相加小于等于n的质数的最小公倍数有几个,此时就要将ans[i]和ans[i – 1]联系起来了。
ans[i]= ans[i - 1] + a[i][0];
为什么这样子,会不会有重复呢?
相加为i的各种质数和相加为i – 1的各种质数的最小公倍数肯定不相同。
所以,记忆化搜索完成。其实,就是递推,由i - 1推出i。
/*
Pro: 0
Sol:
date:
*/
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
#include <set>
#include <algorithm>
using namespace std;
#define maxn 1001
long long ans[maxn],a[maxn][maxn];
int prime[maxn],primeNumber,flag[maxn];
void getprime(){
memset(flag,true,sizeof(flag));
for(int i = 2; i < maxn; i ++){
for(int j = i + i; j < maxn; j += i)
flag[j] = false;
}
primeNumber = 0;
for(int i = 2; i < maxn; i ++){
if(flag[i]) prime[primeNumber ++] = i;
}
}
void getans(){
memset(a, 0, sizeof(a));
for(int i = 2; i < maxn; i ++){//对于每一个可以分解质因数的i分解
for(int j = 0; j < primeNumber; j ++){
if(prime[j] > i) break;
long long tmp = 1;
while(tmp <= i){
tmp *= prime[j];//prime[j]可以取很多个,针对每一个i
if(i - tmp > 0)
for(int k = j + 1; k < primeNumber; ++ k)
a[i][j] += a[i - tmp][k];//这个关系式极其重要
else if(i - tmp == 0){//等于0,那么只有一种取法,需要加上去
a[i][j] ++; break;
}
else break;
}
}
}
ans[0] = 0; ans[1] = 1;
for(int i = 2; i < maxn; i ++)
for(int j = 1; j < primeNumber; j ++)
a[i][0] += a[i][j];
for(int i = 2; i < maxn; i ++)
ans[i] = ans[i - 1] + a[i][0];
}
void init(){
getprime();
getans();
}
int n;
int main(){
init();
while(scanf("%d",&n) != EOF){
printf("%I64d\n",ans[n]);
}
return 0;
}
再贴一个dfs的代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 1100;
const double esp = 1e-6;
int pri[N], vis[N];
__int64 ans[N][N];
int cnt;
void prime()
{
int i, j;
cnt = 0;
for(i=2; i<N; i++)
if(!vis[i])
{
pri[cnt++] = i;
for(j=i+i; j<N; j+=i) vis[j] = 1;
}
}
__int64 dp(int m, int n, int i)
{
if(ans[n][i]!=-1) return ans[n][i];
if(i>m) return ans[n][i] = 1;
ans[n][i] = dp(m, n, i+1);//首先是第i个质数不存在的情况
int k = pri[i];
while(k<=n)
{
ans[n][i] += dp(m, n-k, i+1);//然后枚举他的幂次数存在
k *= pri[i];
}
return ans[n][i];
}
int main()
{
int i, n, m;
prime();
while(~scanf("%d", &n))
{
for(i=0; pri[i]<=n; i++);
m = i-1;
memset(ans, -1, sizeof(ans));
printf("%I64d\n", dp(m, n, 0));
}
return 0;
}
好神啊!