现在的位置: 首页 > 综合 > 正文

hdu – 4345 – Memory Control – 数论

2012年10月13日 ⁄ 综合 ⁄ 共 2775字 ⁄ 字号 评论关闭

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: 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;  
}  

好神啊!

抱歉!评论已关闭.