ZOJ 2013 ACM/ICPC Asia Regional Changsha Online G Goldbach

http://acm.zju.edu.cn/changsha/showProblem.do?problemCode=G

 

题目大意是任给一个数,计算出这个数是否可以由一个到三个素数通过加法和乘法得到的方法总数,比如说7有4种方法得到,
分别是7, 2+2+3, 2*2+3, 2+5.然后得到的方法数可能会很大,所以最后的结果的mod上1,000,000,007.
 
解题的思路非常简单,就是会超时,当初写的代码非常简单,当初傻傻地用判断一个数是否是素数的方法来做,超时超得很离谱。然后这个题目的关键是计算前的预处理,先预处理出两个素素数相乘的,然后就可以直接判断某个数是否可以由两个素数相乘得到,再预处理出两个数相加的,就可以直接判断出某个数可以有多少种由两个素数相加的方法。刚开始计算出那些素数的方法也很重要,这里是采用了优化过的素数筛的方法来筛选出80000以下所有素数的,具体的筛选方法在下面这个网站里。

 

#include<cstdio>
#include<cstring>
#include<cmath>
const int N = 80000;
#pragma warning(disable:4996)
const int MOD = 1000000007;

int num;
int sum; //计算80000内素数的个数
long long ans;
int b[8000]; //存素数
bool prime[80005]; //标记下标是否是素数
bool multi[80005]; //标记下标是否是两个素数相乘得到
int add[80005]; //计算下标可以由多少对素数相加得到

//计算出80000里面的所有素数
//这边用的是素数筛的优化筛法
void Makeprime()
{
    sum = 0;
    memset(prime, 1, sizeof(prime));
    prime[0] = prime[1] = 0;

    //所有的偶数全部干掉
    for(int i = 4; i <= N; i += 2)
        prime[i] = 0;

    //把素数放进b里
    b[sum++] = 2;

    for(int i = 3; i <= N; i += 2)

        //第一层取平方数
        if(prime[i] == true)
        {
            b[sum++] = i;

            if(i > sqrt(N))
                continue;

            for(int j = i * i; j <= N; j += 2 * i)
                //筛法的第二层。从平方开始如果是存在的就筛掉
                prime[j] = false;

            //第二层结束
        }

    //第一层结束
    return;
}

//预处理两个素数相加还有两个素数相乘
void Calctwo()
{
    memset(multi, 0, sizeof(multi));
    memset(add, 0, sizeof(add));

    for(int i = 0; i < sum - 1; i++)
        for(int j = i; j < sum; j++)
        {
            //小心别超了int的范围,直接用下标来标记是否可由两个素数相乘得到
            //两个素数相乘一定不会出现重复的情况,所以只要标记是否有出现过行
            if((long long)b[i] * (long long)b[j] <= 80000)
                multi[b[i] * b[j]] = 1;
            else
                break;
        }

    for(int i = 0; i < sum - 1; i++)
        for(int j = i; j < sum; j++)
        {
            //两个数相加的情况非常多,预处理后,可以大量减少计算的次数
            if(b[i] + b[j] <= 80000)
                add[b[i] + b[j]] ++;
            else
                break;
        }

    return;
}

int main()
{
    Makeprime();
    Calctwo();

    while(scanf("%d", &num) != EOF)
    {
        ans = 0;

        //直接是素数
        if(prime[num])
            ans++;

        //两个素数相乘
        if(multi[num])
            ans++;

        //两个素数相加
        ans += add[num];

        //两个素数相乘再加上一个素数
        for(int i = 0; i < sum && b[i] < num; i++)
        {
            int tmp = num - b[i];

            if(multi[tmp])
                ans++;
        }

        //三个数相乘
        if(!prime[num] && !multi[num])
            for(int i = 0; i < sum; i++)
            {
                if(num % b[i])
                    continue;

                if(multi[num / b[i]])
                    ans++;

                break;
            }

        //三个数相加
        if(num % 2)
        {
            long long tp = 0;

            for(int i = 0; i < sum && b[i] < num; i++)  // 奇 + 奇 + 奇
            {
                int tmp = num - b[i];
                tp += add[tmp];

                //有重复,去重不如直接加上没有重的部分,然后直接除以三
                //没有重复的部分分别是三个都相等的还有有两个是相等的
                if(b[i] * 3 == num)
                    tp += 1;

                if(tmp % 2 == 0)
                    if(prime[tmp / 2])
                        tp++;
            }

            if(tp % 3)
                printf("%d\n", tp);

            ans += tp / 3;
        }
        // 奇 + 奇 + 2
        else
        {
            num -= 2;
            ans += add[num];
        }

        printf("%lld\n", ans % MOD);
    }

    return 0;
}
View Code
posted @ 2014-04-13 17:01  Tank..  阅读(158)  评论(0编辑  收藏  举报