Loading

MR/PR学习笔记

1 Miller-Rabin

1.1 朴素筛法

  • 如何判断一个数是不是质数?
  • 如果 \(2,3,\cdots,\lfloor\sqrt n\rfloor\not|~n\),那么 \(n\) 就是质数。
  • 算法复杂度 \(O(\sqrt n)\),有点慢。

可惜,如果要保证答案正确,似乎并没有什么好方法了……

等等,答案正确

1.2 引入&铺垫

  • 没错,我们要使用不正确的算法过题了。

1.2.1 为什么“不正确”的算法能过题

  • 如果你想在机房颓废,但是不知道老师在不在,你可以使用以下这个策略:
  1. 颓一分钟,有 \(0.8\) 的概率被老师发现。
  2. 重复第 \(1\)\(10\) 次。
  3. 如果都没被老师发现,就认为老师不在。

这样的话,我们唯一的失误就是老师在,你颓废 \(10\) 次没被发现

这个的概率是多少呢?\(0.2^{10}=0.0000001024\)。但是如果你只重复一次,失败的概率高达 \(0.2\)

这说明了什么?

我们只要用一个正确率相对高的方法随机采样判断,失误概率可以指数级下降。

1.2.2 一些前置知识

  • 这些知识你可能小学就学过了,但是不要紧,我们来复习一下。下文中 \(p\) 都是素数。
  • 如果 \((a,p)=1\)\(a^{p-1}\equiv1\pmod p\)
  • 如果 \(x^2\equiv1\pmod p\),则 \(x\equiv\pm1\pmod p\)

我并不知道为什么很多人说第二个公式 \(p\) 必须是奇素数,\(2\) 应该也可以吧

1.3 正片开始

  • 我们先钦定一个质数 \(p\)。你可以随便钦定,比如 \(2\)\(3\)\(13\),甚至是 \(19260817\)
  • Case 1:显然如果我们判断的数 \(x=p\),那么 \(x\) 是素数。
  • Case 2:显然如果 \(p|x\)\(x>p\),那么 \(x\) 是合数。
  • Case 3:显然如果 \(p^{x-1}\not\equiv1\pmod x\),那么 \(x\) 是合数。
  • Case 4:以上三种都没有满足。
  1. 初始化 \(k=x-1\)
  2. 如果 \(k\) 是奇数,我们就认定 \(x\) 是素数(没错就这么草率)。
  3. \(k=\frac{k}{2},t=p^k\%x\)
  4. 如果 \(t\not\equiv\pm1\pmod x\),我们就知道 \(x\) 不是素数了。
  5. 如果 \(t\equiv-1\pmod x\),我们就认定 \(x\) 是素数(没错就这么草率)。
  6. 回到第一步。

注意到在判断 \(x\) 不是素数的时候,我们是肯定的,但是在返回 \(x\) 是素数的时候,我用上了草率两字。不难举出很多判断是素数时的反例,然而大多数情况下,判断还是相对准确的。

因此结合 1.2.1 中的内容,只要我们多判断几次,正确率就能接近百分之百了。

注意到我们并没有限制 \(p\),所以 \(p\) 可以任意取正整数。由于题解中都这么写,我推荐大家使用 \(2,3,5,7,11,13,19,61,2333,24251\) 这十个数,听说它能顺利判断出所有 long long 范围的质数。

1.4 实现

1.4.1 小坑点

由于模数 \(x\)long long 范围,两个模 \(x\) 意义下的非负整数相乘可能超出 long long 范围,由于 __int128 效率十分感人并且并不能在 NOI 系列比赛中使用,笔者采用龟速乘实现 \(a\times b\%c\)

1.4.2 代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
   int s=0;
   char ch=getchar();
   while(ch<'0'||ch>'9') ch=getchar();
   while(ch>='0'&&ch<='9') s=s*10+(ch&15),ch=getchar();
   return s;
}
int mul(int x,int y,int p)
{
	int res=0;
	for(int now=x; y; y>>=1,now<<=1,now%=p) (y&1)&&(res+=now,res%=p);
	return res;
}
int qp(int x,int y,int p)
{
	int res=1;
	for(int now=x; y; y>>=1,now=mul(now,now,p)) (y&1)&&(res=mul(res,now,p));
	return res;
}
bool Miller_Rabin(int x,int p)
{
	for(int t,k=x-1; !(k&1);)
	{
		k>>=1,t=qp(p,k,x);
		if(t!=1&&t!=x-1) return 0;
		if(t==x-1) return 1;
	}
	return 1;
}
const int P[10]={2,3,5,7,11,13,19,61,2333,24251};
bool is_prime(int x)
{
	for(int i=0; i<10; ++i) 
	{
		if(x==P[i]) return 1;
		if(x%P[i]==0) return 0;
		if(qp(P[i]%x,x-1,x)!=1) return 0;
		if(!Miller_Rabin(x,P[i]%x)) return 0;
	}
	return 1;
}
signed main()
{
    int n=read();
    if(is_prime(n)) puts("114514");
    else puts("1919810");
    return 0;
}

2 Pollard-Rho

2.1 朴素筛法

  • 如何分解质因数?
  • 找出 \(x\) 的所有因子:对所有小于 \(\sqrt n\) 的质数试除。
  • 算法复杂度 \(O(\sqrt n)\),有点慢。

可惜,如果要保证算法靠谱,似乎并没有什么好方法了……

等等,算法靠谱

2.2 跳过op

小手一抖,op 没有,我们直入正题。

2.2.1 从 MR 到 PR

我们已经会高效判断 \(x\) 是不是质数了。

如果我们要分解 \(x\),并且知道 \(x\) 不是质数,我们必然知道 \(x\) 有至少一个不超过 \(\sqrt x\) 的质因子。

如果我们已经找到了一个数 \(t(t>1)\) 使得 \(t|x\),我们就可以递归处理 \(t\)\(\frac{x}{t}\) 了。但是 \(t\) 并不好找。

这时,一个神仙出现了:找不到 \(t\),我们就找 \(t\) 的倍数,这样就增加了 \(\sqrt x\) 倍的获奖概率!(为什么是 \(\sqrt x\) 倍的中奖概率呢,因为必然存在一个 \(t<\sqrt x\),而 \(t\) 的整数倍在 \(x\) 以内至少存在 \(\frac{x}{t}\geq\sqrt x\) 个)

如果我们每次尝试成功的概率是 \(\sqrt x\)据说期望复杂度就是 \(\text O(n^\frac{1}{4})\)

但是找到 \(t\) 的倍数又怎么转化回 \(t\) 呢……没错,就是 \(\gcd\)

因此我们只要随便选几个数 \(\gcd\) 就行了。

2.2.2 神奇的优化

  • 神奇的随机算法,floyd 判圈。
  • gcd 优化。
  • 一个我也看不懂的神奇优化,见讨论区。

2.3 代码(被卡常)

由于太逊就不给了。

2.4 悲报

我不懂为什么别人这么快啊……最后还是 system 过了这题/ll

posted @ 2021-01-13 21:59  dXqwq  阅读(249)  评论(0编辑  收藏  举报