挑战程序设计解题报告 2.6.2素数

本文提供了三道程序设计题目(POJ3126、POJ3421、POJ3292)的详细解答思路及代码实现,涵盖素数判断、因数分解、特定数列的生成等算法问题。

挑战程序设计解题报告 2.6.2素数

1.POJ 3126

        题意通过将一个四位素数,每次变化一位使其成为一个新的素数,重复这个操作直到变化成给定的数据,成功就输出经过了多少步,失败则输出Impossible,看题干中的1033->1733->3733->3739->3779->8779->8179就可以看出来,这道题我直接使用了BFS的思想,首先建立一张素数表,然后生成新的数字,检查合法性,然后加入队列,直到符合要求或者队列为空为止。

       这里生成新数字有一个技巧,可以通过数学方式计算出来,通过除法和取余即可,具体看代码:

const int maxn = 10000;
bool isprime[maxn];
bool vis[maxn];
int Pow[] = {1, 10, 100, 1000, 10000};   //用于快捷生成数字的数组

void init()
{
    memset(isprime, true, sizeof(isprime));
    isprime[1] = false;
    for(int i = 2; i < maxn; ++i)
        if(isprime[i])
            for(int j = i * 2; j < maxn; j +=i)
                isprime[j] = false;
}    //埃式筛法素数表

struct Num
{
    Num(int info, int step)
        :info(info), step(step){}
    int info;
    int step;
};
queue<Num>q;
int bfs(int n, int m)
{
    while(!q.empty())
        q.pop();
    q.push(Num(n, 0));
    vis[n] = true;
    while(!q.empty())
    {
        Num fr = q.front();
        q.pop();
        if(fr.info == m)
            return fr.step;
        for(int i = 1; i <= 4; ++i)    //操作4位数字上的每一位
        {
            int Begin = fr.info / Pow[i];    //取出前面的数字,比如从1033 生成 1733 这里Begin就等于1  End就等于33
            int End = fr.info % Pow[i - 1];
            for(int j = 0; j < 10; ++j)    //中间的7是通过这里实现的
            {
                int nNum = Begin * Pow[i] + j * Pow[i - 1] + End;  //合成新数字
                if(!vis[nNum] && isprime[nNum] && nNum >= 1000)   //检测数字合法性,nNum一定要大于1000,因为生成的数字很有可能有前导零
                {
                    q.push(Num(nNum, fr.step + 1));
                    vis[nNum] = true;
                }
            }
        }
    }
    return -1;
}

int main()
{
#ifdef LOCAL
    ///freopen("in.txt", "r", stdin);
    ///freopen("out.txt", "w", stdout);
#endif // LOCAL
    init();
    int t;
    int n, m;
    cin >> t;
    while(t--)
    {
        cin >> n >> m;
        memset(vis, false, sizeof(vis));
        int ans = bfs(n, m);
        if(ans == -1)
            cout << "Impossible" << endl;
        else
            cout << ans << endl;
    }
    return 0;
}

2.POJ 3421

        题意没看懂,但是看样例的意思就是说,找到一个数字的所有素因子,和这些素因子的全排列个数,比如100,素因子是2 2 5 5,全排列是2255 2525 2552 5225 5252 5522,所以输出4 6,理解了题意做法就简单了,这里计算排列组合数有一个公式,有重复数据的全排列个数为A! / (a! * b! * c! .......) 其中A表示数据总的数量,a,b,c,d分别表示不同重复数据的个数,比如这里2 2 5 5就是4! / (2! * 2!) 即为6,再比如2 2 2 3 3 4 5 5 5 6就是10! /(3! * 2! * 1! * 3! * 1!)

        那么很明显我们使用C++的map会使这个过程变得更简单,另外不管是分子还是分母都不会超过long long的范围,因为分子最大就20!,并且分母肯定不大于分子,所以直接计算即可,当然可以使用init函数,提前生成一个阶乘表。

代码如下:

LL fac[21];

void init()
{
    fac[1] = 1;
    LL ans = 1;
    for(LL i = 2; i <= 20; i++)
    {
        ans *= i;
        fac[i] = ans;    //阶乘表初始化
    }
}

int main()
{
#ifdef LOCAL
    ///freopen("in.txt", "r", stdin);
    ///freopen("out.txt", "w", stdout);
#endif // LOCAL
    init();
    int num;
    while(cin >> num)
    {
        map<int, int>m;    //while作用域的局部变量,不用每次都初始化
        int k = 2;
        while(k * k <= num)
        {
            while(num % k == 0)
            {
                m[k]++;
                num /= k;
            }
            k++;
        }
        if(num != 1)
            m[num]++;    //因式分解
        int sum1 = 0;
        LL sum2 = 1;

        map<int, int>::iterator it = m.begin();
        for(; it != m.end(); ++it)
        {
            sum1 += it->second;   //分子
            sum2 *= fac[it->second];   //分母
        }
        cout << sum1 << " " << fac[sum1] / sum2 << endl;
    }
    return 0;
}

3.POJ 3292

        首先在题目中说了几种数字,第一种H-numbers,指的是4n+1这种数字,第二种H-primes,指的是因子只有1和它本身,第三种H-semi-primes,指的是因子有且仅有两个H-primes构成,比如125不满足,因为它是5 * 25,其中25是有5 * 5构成,他不是一个H-primes,剩下的数字是H-composite.

3.1 解法1

         这道题我第一次写的时候用了一种特别笨的方法,令我吃惊的是,居然没有超时,我随便看了几页好像是这种写法是G++组时间最长的,唉,好尴尬。说一说我的方法吧。我首先把里面的H-primes挑选出来,我发现当一个数字的因子有5的时候,这些因子每隔4次出现一次,比如5 * 9 = 45, 5 * 13 = 65,45 49 53 57 61 65间隔为4,并且当一个数字的因子含有9的时候间隔是8,含有13的时候间隔是12。。。依次类推,所以我把45 65  等等这些数字标记起来,表示他们肯定不是H-primes

        接着从里面挑选出没有标记的数字,也就是H-primes,然后两两相乘放入set去掉重复,既是H-semi-primes,然后把set取出来作为vector

        最后,主函数中,利用二分查找即可确定前面还有多少个数字。

        代码如下:

//内存 7580K   时间  469MS
const int maxn = 250001;
bool vis[maxn];
vector<LL>v, n;
set<int>t;

void init()
{
    for(int i = 1; i <= 900; ++i)
    {
        int k = 4 * i * i + 2 * i;
        while(4 * k + 1 <= 1000001)
        {
            vis[k] = true;
            k += (i * 4) + 1;
        }
    }    //标记非<strong>H</strong>-primes
    for(int i = 1; i < maxn; ++i)
        if(!vis[i])
            v.push_back(i);    //获取<strong>H</strong>-primes

    for(int i = 0; i < v.size(); ++i)
        for(int j = 0; j < v.size(); ++j)
            if((v[i] * 4 + 1) * (v[j] * 4 + 1) <= 1000001)
                t.insert((v[i] * 4 + 1) * (v[j] * 4 + 1));    //生成<strong>H</strong>-semi-primes
            else
                break;
    set<int>::iterator it = t.begin();    //准备使用二分查找
    for(; it != t.end(); ++it)
    {
        n.push_back(*it);
    }
}

int main()
{
#ifdef LOCAL
    ///freopen("in.txt", "r", stdin);
    ///freopen("out.txt", "w", stdout);
#endif // LOCAL
    init();
    int num;
    while(cin >> num && num > 0)
    {
        cout << num << " " << upper_bound(n.begin(), n.end(), num) - n.begin()<< endl;    //库函数二分
    }
    return 0;
}

3.2 解法2

       后来我想到,我都在打表了,我为什么要打H-primes表,直接打H-semi-primes不就好了吗,只要生成H-semi-primes这个数字的因子是H-primes,那它暂时就是一个H-semi-primes数,具体做法和素数筛法简直一模一样,从5开始,如果生成res(代码中的变量)的两个数字都是H-primes,那它就暂时是一个H-semi-primes数字(之所以是暂时的,根据代码举例子说明),但是如果他的两个数字,有一个不是H-primes,可以肯定它绝对是一个H-composite。结合代码再说明

     代码如下

//内存  8532K 时间 47MS
const int maxn = 1000001;
int num[maxn + 5];    //自动初始化为0也就是<strong>H</strong>-primes
int Count[maxn + 5];

void init()
{
    for(int i = 5; i <= maxn; i += 4)
        for(int j = 5; j <= maxn; j += 4)
        {
            int res = i * j;   //不会溢出,因为根本不会出现i和j都特别大的时候
            if(res > maxn)
                break;
            if(num[i] == 0 && num[j] == 0)
                num[res] = 1;
            else
                num[res] = -1;    //解决遗留问题
        }
    int nCount = 0;
    for(int i = 1; i <= maxn; ++i)
    {
        if(num[i] == 1)
            nCount++;
        Count[i] = nCount;
    }
}

int main()
{
    init();
    int n;
    while(scanf("%d", &n) && n)
    {
        printf("%d %d\n", n, Count[n]);
    }
}
        之前为什么说是暂时的,因为我从5开始标记 ,一开始由于65还没有被标记为 H-semi-primes,所以5 * 65这组会被标记为 H-semi-primes,这是不合题意的,但是请看第二个for循环的起点,始终是5,虽然5 * 65这组,一开始会被忽略,但是25 * 13这组是不会被忽略的,因为25之前已经被标记为 H-semi-primes,这里就不符合两组都是 H-primes的条件了,5 * 65 == 25 * 13这组会被重新标记为 H-composite,也就是说 一开始忽略掉的数据,在后来会被补上。可以用几组数据验证一下。

        现在的速度就快了很多了,10倍提速啊,第二种写法参考了小優YoU学姐的做法,之间我想到了第二种解法,但是没有想到怎么处理形如5 * 65这种,去掉标记的做法,不过第一种虽然慢,但是我觉得还是挺好的,毕竟是自己做的。很有意义啊!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gscsdlz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值