挑战程序设计解题报告 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这种,去掉标记的做法,不过第一种虽然慢,但是我觉得还是挺好的,毕竟是自己做的。很有意义啊!

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

被折叠的 条评论
为什么被折叠?



