P4397聪明的燕姿 题解 & Miller~Rabin 质数判定

涉及质数的时间复杂度都是玄学的。

——题记

传送门

由整数唯一分解定理:i=1kpici

有该正整数的正约数为:i=1k(j=0cipij)

即我们要求有多少个数满足 i=1k(j=0cipij)=S

然后就搜索 + 剪枝:

枚举第 i 个质数是否出现,出现多少次,显然是对的。

优化 1 :当我们枚举到的质数大于 S 时,我们直接判断 S1 是不是质数即可,因为此时任意 p2>S

优化 2 :当要判断大于 1e6 的数是否为质数时,使用 Miller Rabin 质数判定


扩:Miller Rabin 质数判定:

首先:该算法本质上是一种随机化算法,能在O(klog3n)的时间复杂度下快速判断出一个数是否是素数,但具有一定的错误概率。不过在一定数据范围内,通过一些技巧可以使其不出错。

由费马小定理可知:对任意素数 p 和与其互质的整数 a,有 ap11 (modp)

考虑将其逆过来,对一个数 p,随机选取一个小于它的 a,若 ap11 (modp),是否可以证明它是质数

很明显是不可以的,一个反例就是 561(当然这种数有无限个,可以去搜卡迈克尔数

引入二次探测定理:对于质数 px21 (modp),则小于 p 的解只有两个: x=1x=p1

证明:

原式可以转换为 x210 (modp)=>(x+1)(x1)0 (modp),因为 p 的因数只有 1p,所以小于 p 的解只有 x=1x=p1

证毕

然后考虑结合两个定理,进行素数判断。

对于 p1,若它为奇数,直接特判

否则将它分为 2k×t(t 为奇数),随机选取一个 a,先计算 at,然后让其自乘,判断其解是否全为 1 ,或者在非最后一个数的情况下出现 p1

如果都满足,则我们认为这个数是质数

因为错误率是 14

所以我们可以选 2,3,5,7,11,13,17,19 多个数进行判断,降低错误率

当然,也可以选择背诵

int 范围内选 {2,7,61}

long long 范围内选 {2,325,9375,28178,450775,9780504,1795265022}

可以保证 100% 不会错

因为数已经定下来,所以可能会导致选的数是 p 的倍数,这是要特判,然后 continue


最后,分析一下该算法时间复杂度:一次找到答案的时间不会超过 S,所以时间复杂度是 O(AnsS),玄学至极

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=100005;
int T,S;
int ans[N],tot;
bool prime[N];
int zs[N],idx;
int ksm(int ds,int zs,int mod)
{
int re=1;
while(zs)
{
if(zs&1) re=1ll*ds*re%mod;
ds=1ll*ds*ds%mod;
zs=zs/2;
}
return re;
}
bool MRtest(int x)
{
if(x%2==0||x<3) return x==2;
int zs=x-1,sl=0;
while(zs%2==0) zs=zs/2,sl++;
int ud[3]={2,7,61};
for(int i=0;i<3;i++)
{
int v=ksm(ud[i],zs,x);
if(v==1||v==x-1||v==0) continue;
for(int j=1;j<=sl;j++)
{
v=1ll*v*v%x;
if(v==x-1&&j!=sl){v=1;break;}
if(v==1) return false;
}
if(v!=1) return false;
}
return true;
}
void dfs(int wz,int sum,int now)
{
if(sum==1)
{
ans[++tot]=now;
return ;
}
if(sum<zs[wz]+1) return ;
if(MRtest(sum-1)) ans[++tot]=(sum-1)*now;
for(int i=wz;zs[i]*zs[i]<=sum;i++)
{
int zh=zs[i]+1,t=zs[i];
for(;zh<=sum;t*=zs[i],zh+=t)
if(sum%zh==0)
dfs(i+1,sum/zh,now*t);
}
}
signed main()
{
prime[1]=true;
for(int i=2;i<=100000;i++)
{
if(!prime[i]) zs[++idx]=i;
for(int j=1;j<=idx&&zs[j]*i<=100000;j++)
{
prime[zs[j]*i]=true;
if(i%zs[j]==0) break;
}
}
while(scanf("%lld",&S)!=EOF)
{
tot=0;
dfs(1,S,1);
sort(ans+1,ans+tot+1);
printf("%lld\n",tot);
for(int i=1;i<=tot;i++)
printf("%lld ",ans[i]);
if(tot!=0) puts("");
}
return 0;
}

遇到质数的题,大胆DFS,多剪枝就好了

——后记

posted @   傻阙的缺  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示