Prime Cryptarithm实现(USACO)

本文针对牛式问题提出了一种有效的解决方法。首先通过枚举找出所有可能的三位数和两位数,然后验证这些数字是否满足题目设定的条件。文章还提供了一种使用哈希表的优化方案,以提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.原题呈现

翻译后的题目如下图所示:
这里写图片描述

2.实现思路

根据上述原题陈述可知:
要满足的条件有以下几个(在这里我们设“牛式”中的三位数为A,二位数为B):
(1)A中的每一个数需要在给定的数组中选出。
(2)B中的每一个数需要在给定的数组中选出。
(3)B的个位乘以A的得到的数中,每一个数需要在给定的数组中选出,并且是一个三位数。
(4)B的十位乘以A的得到的数中,每一个数需要在给定的数组中选出,并且是一个三位数。
(5)A*B的每一位的数都在数组中选出,并且是一个四位数。
根据上述的要求可以有以下思路:
(1)根据给出的数组找出所有可能的三位数A:n*n*n.
(2)根据给出的数组找出所有可能的二位数B:n*n.
(3)枚举出每一个A*B计算后的结果是否满足上述的五种约束条件,如果满足则“牛式”的总数加1.

3.具体实现

根据上述思路具体实现的代码如下所示:

#include <iostream>
#include <algorithm>
#include <fstream>
using namespace std;
int c[10];
int check(int );
int main()
{
    int a[10];
    int b[1000+5];
    int sum1,sum2;
    int n;
    ifstream in("crypt1.in",ios::in);
    ofstream out("crypt1.out",ios::out);
    in >> n;
    for(int i = 0; i < n; i++)
    {
        in >> a[i];
        c[a[i]] = 1;
    }
    sort(a,a+n);
    int p = 0;
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < n; j++)
        {
            for(int k = 0; k < n; k++)
            {
                b[p] = a[i]*100+a[j]*10+a[k];
                p++;
            }
        }
    }
    int sum = 0;
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < n; j++)
        {
            for(int k = 0; k < p; k++)
            {
                sum1 = a[i]*b[k];
                sum2 = a[j]*b[k];
                if(check(sum1)||check(sum2)||check(sum1*10+sum2))
                {
                    continue;
                }
                if( sum1<1000 && sum1>=100&& sum2<1000 && sum2>=100 && sum1*10+sum2 < 10000 && sum1*10+sum2 >= 1000)
                {
                    sum++;
                }
                else
                {
                    break;
                }
            }
        }
    }
    out << sum << endl;
    return 0;
}
int check(int sum)
{
    while(sum)
    {
        if(!c[sum%10])
        {
            return 1;
        }
        sum = sum / 10;
    }
    return 0;
}

以下我们详细分析上述代码:
(1)找出由给出的数组组合成的所有可能三位数A,具体试下的代码如下所示,通过三层for()循环实现,并且通过p来记录所有三位数的总数。

for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < n; j++)
        {
            for(int k = 0; k < n; k++)
            {
                b[p] = a[i]*100+a[j]*10+a[k];
                p++;
            }
        }
    }

(2)找出有给出的数组组合成的所有可能两位数B,并将B与每一个三位数进行乘法运算,具体的实现过程如下:

for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < n; j++)
        {
            for(int k = 0; k < p; k++)
            {
                sum1 = a[i]*b[k];
                sum2 = a[j]*b[k];
                if(check(sum1)||check(sum2)||check(sum1*10+sum2))
                {
                    continue;
                }
                if( sum1<1000 && sum1>=100&& sum2<1000 && sum2>=100 && sum1*10+sum2 < 10000 && sum1*10+sum2 >= 1000)
                {
                    sum++;
                }
                else
                {
                    break;
                }
            }
        }
    }

在上述代码中,通过一下部分来判定是否满足题目要求的五个约束条件:

if(check(sum1)||check(sum2)||check(sum1*10+sum2))
                {
                    continue;
                }
                if( sum1<1000 && sum1>=100&& sum2<1000 && sum2>=100 && sum1*10+sum2 < 10000 && sum1*10+sum2 >= 1000)
                {
                    sum++;
                }

4.遇到的问题

在实现的过程中,通过枚举的方式去实现,但是没有考虑到“牛式”中数字A分别乘以数字B得到的数的各个位数都满足在数组中。

5.参考的其他思路

可以用用哈希表设计O(1)的穷举法.
两个乘数的位数是固定的,第一个数一定是100到999之间,第二个数只能是10到99之间。
既然如此,那么我们完全没有必要用DFS去按数位搜索,直接穷举100到999间的所有数以及10到99间的所有数。
然后计算乘积与两个分部乘积,判断乘积是否为四位数(是否在1000到9999之间),以及两个分部乘积的位数是否符合要求。
再判断他们是否由给定的数字组成,如果上面的判断都通过了,则计数器加1。
穷举的个数是常数,第一个数有900种可能,第二个数有90种可能,一共有81000种可能。判断是否由给定数字组成的时间复杂度是O(n)。故整个算法的时间复杂度是O(81000n)=O(n)。
然而这还有改进的余地,将可使用数字存在哈希表而不是线性表里。
定义hash[i]:
如果数字i是可以被使用的,则hash[i]=1否则为0。
利用这hash结构,要判断一个固定位数的数是否由给定数字组成,复杂度为O(1)。
故整个算法的时间复杂度也是O(1)。

//代码从网站上摘录
#include <cstdio>
int n,b[11],k,ans;
int hash(int v){
    while (v){
        if (!b[v%10]) return 0;
        v/=10;
    }
    return 1;
}
int main(){
    freopen("crypt1.in","r",stdin);
    freopen("crypt1.out","w",stdout);
    scanf("%d",&n);
    for (int i(1);i<=n;i++){
        scanf("%d",&k);
        b[k]=1;
    }
    for (int i(111);i<1000;i++){
        if (hash(i)){
            for (int j(11);j<100;j++){

                if (i*j<10000 && i*(j/10)<1000 && i*(j%10)<1000 && hash(j) && hash(i*(j%10)) && hash(i*(j/10)) && hash(i*j)) {
                        ans++;
                       // printf("%d * %d = %d0 + %d = %d \n",i,j,i*(j/10),i*(j%10),i*j);
                }
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

6.相关链接

【1】Prime Cryptarithm 原题
【2】Prime Cryptarithm 翻译

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值