http://acm.hdu.edu.cn/showproblem.php?pid=4345
问题即是求相加小于等于n的质数的最小公倍数有几个。推荐网站:http://oeis.org/,只需将前面几个数字的答案输入,即可得出序列,打表即过(每想到这里,我就想笑)。
解题报告说用记忆化搜索,没想明白,估计就是跟dp差不多,用前面的去推后面的。
今晨参悟http://blog.csdn.net/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; }