hdu - 4345 - Memory Control - 数论

来源:互联网 发布:pc安装mac os x10.10 编辑:程序博客网 时间:2024/04/29 03:29

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]那种了。

 于是求对应的方案个数就是剩下的i-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: 0Sol:date:*/#include <cstdio>#include <iostream>#include <cstring>#include <cmath>#include <set>#include <algorithm>using namespace std;#define maxn 1001long 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;  }  

好神啊!


原创粉丝点击