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