欧拉函数线性筛
对于素数ppp,
φ(p∗i)={p−1i=1p∗φ(i)p∣i(p−1)∗φ(i)p∤i\varphi (p*i)= \begin{cases} p-1& i=1\\ p*\varphi(i)& p \mid i\\ (p-1)*\varphi(i) & p \nmid i \end{cases}φ(p∗i)=⎩⎪⎨⎪⎧p−1p∗φ(i)(p−1)∗φ(i)i=1p∣ip∤i
证明:
i=1i=1i=1 显然
i≠1i \neq1i=1根据
φ(i)=i∏pi−1pi\varphi(i)=i \prod \frac {p_i-1}{p_i}φ(i)=i∏pipi−1
p∣ip \mid ip∣i时除掉iii中的ppp
p∤ip \nmid ip∤i 时除掉ppp
魔改欧拉筛素数即可
欧拉定理
当(a,p)=1(a,p)=1(a,p)=1
aφ(p)≡1(modp)a^{\varphi(p)}\equiv 1 \pmod paφ(p)≡1(modp)
即
ab≡ab%φ(p)(modp)a^{b}\equiv a^{b\%\varphi(p)} \pmod pab≡ab%φ(p)(modp)
扩展欧拉定理
当b≥φ(p)b\geq\varphi(p)b≥φ(p)
ab≡ab%φ(p)+φ(p)(modp)a^{b}\equiv a^{b\%\varphi(p)+\varphi(p)} \pmod pab≡ab%φ(p)+φ(p)(modp)
注意不要求互质
Miller Rabbin 算法
可以O(logx)O(log_x)O(logx)判断素数(有极小概率出错)
费马小定理:ppp为素数,a≠pa \neq pa=p,ap−1≡1(modp)a^{p-1} \equiv 1 (mod \text{ } p)ap−1≡1(mod p)
我有一个对这个命题的十分美妙的证明,可惜这里空白太小,我写不下
二次探测定理:ppp为素数,x2≡1(modp)x^2 \equiv 1 (mod\text{ } p)x2≡1(mod p)的解为x=1x=1x=1或x=p−1x=p-1x=p−1
证明见二次剩余
算法流程:设要判断的数为xxx
- 特判0,1,20,1,20,1,2,偶数
- 取一个较小素数ppp,设a,ba,ba,b满足2a×b=x−12^a \times b=x-12a×b=x−1
- 设x′≡pb(modx)x' \equiv p ^ b (mod \text{ } x)x′≡pb(mod x),不断取平方并对xxx取模,若结果为111,用二次探测判断。
- 重复aaa次后x′≡px−1(modx)x' \equiv p ^ {x-1} (mod \text{ } x)x′≡px−1(mod x),用费马小定理判断
例题:hdu2138 How many prime numbers
题意:给NNN个数,数数有多少个素数
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
typedef long long ll;
using namespace std;
int qpow(int a,int p,int m)
{int ans=1;while (p){if (p&1) ans=((ll)ans*a)%m;a=((ll)a*a)%m,p>>=1;}return ans;
}
int pri[]={2,3,5,7,11,13,17,19,23,29};
bool check(int x)
{if (x==2) return true;if (!(x&1)) return false;int a=0,b=x-1;while (!(b&1)) ++a,b>>=1;for (int i=0;pri[i]<x&&pri[i]<29;i++){int now=qpow(pri[i],b,x);for (int j=1;j<=a;j++){int t=((ll)now*now)%x;if (t==1&&now!=1&&now!=x-1) return false;now=t;}if (now!=1) return false;}return true;
}
int main()
{int n;while (~scanf("%d",&n)){int ans=0,x;while (n--){scanf("%d",&x);ans+=check(x);} printf("%d\n",ans);}return 0;
}
关于底数选择:
以2,3,7,61,242512,3,7,61,242512,3,7,61,24251为底,1e161e161e16内唯一无法判断的合数为468562482559814685624825598146856248255981
特判一下就可以了
代码不想改了
Pollard-Rho算法
这个算法就更玄学了,可以极快分解质因数。
有多快呢?longlonglong \text{ }longlong long能跑几百个
该算法核心是找到这个数的一个非平凡因子(即不是111和本身,以下简称因子),然后递归就行了
如何找到一个因子呢? 随机。
当然直接随机肯定不现实,需要一定的改进
①优秀的随机函数
定义随机函数fi=fi−12+cf_i=f_{i-1}^2+cfi=fi−12+c,其中ccc为随机常数。
至于为什么要选这个函数……鬼知道啊
②gcdgcdgcd
设给定的数为xxx,我们要找的是d∣xd \mid xd∣x
概率还是低了点
容易想到一个办法:取gcdgcdgcd,大于111就返回gcdgcdgcd的值
这样多了因数的倍数,很大的提高了概率
③倍增路径
每次取一段2k2^k2k个数,在模xxx乘起来,最后取gcdgcdgcd,kkk为当前段数
易知这样是等效的,避免了大量的gcdgcdgcd计算
使用了倍增,将gcdgcdgcd次数从O(n)O(n)O(n)降至O(logn)O(logn)O(logn)
另外不再使用随机值,而是用随机出来的值和上一段最后一个数作差,这样得到数的个数也大幅增加。
这样除非环特别短,否则环上两两的数都会统计入答案。
根据生日悖论,当环长为45时找不到答案的概率小于1e−181e-181e−18
而你的函数是根据CCC改变的,所以根本卡不了
所以如果出锅了,自觉去交一发Hash Killer III
④玄学优化
每一段跑127127127个数更新一次
亲测快了333倍
来自luoguluoguluogu题解区
原理就不得而知了
结合Miller−RabbinMiller-RabbinMiller−Rabbin食用更佳
例题:luogu模板
题意:给个数,是素数输出"Prime",不是素数输出最大质因数
递归,记个全局变量。还可以剪枝。
运用你丰富的卡常技巧就可以通过此题
// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <cstdlib>
#ifdef WIN32
#define lld "%I64d"
#else
#define lld "%lld"
#endif
typedef long long ll;
using namespace std;
inline ll read()
{ll ans=0;char c=getchar();while (!isdigit(c)) c=getchar();while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();return ans;
}
ll base[]={2,3,7,61,24251};
inline ll qmul(const ll& a,const ll& b,const ll& m)
{ll ans=a*b-(ll)((long double)a*b/m+0.5)*m;return ans<0? ans+m:ans;
}
inline ll qpow(ll a,ll p,const ll& m)
{ll ans=1;while (p){if (p&1) ans=qmul(ans,a,m);a=qmul(a,a,m),p>>=1;}return ans;
}
inline bool Miller_Rabbin(const ll& x)
{if (x==1) return false;if (x==2) return true;if (!(x&1)) return false;if (x==46856248255981ll) return false;int a=0;ll b=x-1;while (!(b&1)) ++a,b>>=1;for (register int i=0;i<5&&base[i]<x;i++){ll now=qpow(base[i],b,x);for (register int j=1;j<=a;j++){ll t=qmul(now,now,x);if (t==1&&now!=1&&now!=x-1) return false;now=t;}if (now!=1) return false;}return true;
}
ll ans;
inline ll f(const ll& x,const ll& c,const ll&m){return (qmul(x,x,m)+c)%m;}
inline ll gcd(ll x,ll y)
{static ll t;while (y>0) t=y,y=x%y,x=t;return x;
}
inline ll abso(const ll& x){return x>0? x:-x;}
inline ll Pollard_rho(const ll& x)
{ll s=0,t=0,c=rand()%(x-1)+1,d,len=1,m=1,i;for (;;len<<=1,s=t,m=1){for (i=1;i<=len;i++){t=f(t,c,x);m=qmul(m,abso(t-s),x);if (i%127==0&&(d=gcd(m,x))>1) return d;}if ((d=gcd(m,x))>1) return d;}
}
inline void fact(const ll& x)
{if (x<ans) return;if (Miller_Rabbin(x)) return (void)(ans=max(ans,x));ll d=Pollard_rho(x);if (Miller_Rabbin(d)) ans=max(ans,d);else fact(d);d=x/d;if (Miller_Rabbin(d)) ans=max(ans,d);else fact(d);
}
int main()
{register int T;ll n,d;scanf("%d",&T);while (T--){n=read();ans=0,fact(n),d=ans;if (n==d) printf("Prime\n");else printf(lld"\n",ans);}return 0;
}
原根
占个坑以后补
二次剩余
以下的ppp均为奇素数
定义:方程x2≡n(modp)x^2 \equiv n (mod \text{ }p)x2≡n(mod p)有解,称nnn是modpmod \text{ } pmod p意义下的二次剩余,否则称非二次剩余
说人话:在modpmod \text{ } pmod p下可以开根号
勒让德符号
(np)={1n是p的二次剩余−1n是p的非二次剩余0p∣n(\frac{n}{p})= \begin{cases} 1& \text{n是p的二次剩余}\\ -1& \text{n是p的非二次剩余}\\ 0 & p \mid n \end{cases}(pn)=⎩⎪⎨⎪⎧1−10n是p的二次剩余n是p的非二次剩余p∣n
如无特别说明,下文分数+括号均为勒让德符号
引理
(np)≡np−12(modp)(\frac{n}{p}) \equiv n^{\frac{p-1}{2}} (mod \text{ }p)(pn)≡n2p−1(mod p)
(强调:ppp是奇素数)
证明:
- 当nnn是ppp的二次剩余,设x2≡n(modp)x^2 \equiv n (mod \text{ } p)x2≡n(mod p),则np−12≡xp−1(modp)n^{\frac{p-1}{2}} \equiv x^{p-1} (mod \text{ } p)n2p−1≡xp−1(mod p)。由费马小定理,xp−1≡1(modp)x ^{p-1} \equiv 1 (mod \text{ } p)xp−1≡1(mod p)
- 当nnn是ppp的非二次剩余,即不存在x2≡n(modp)x^2 \equiv n (mod \text{ } p)x2≡n(mod p)。对于任意的aaa,由于a2≠n(modp)a^2 \neq n (mod \text{ } p)a2=n(mod p),所以a≠a−1na \neq a^{-1}na=a−1n。而逆元是互逆的,这样可以把p−1p-1p−1个数配成p−12\frac{p-1}{2}2p−1对,每对数的积为nnn。将所有数乘起来,(p−1)!≡np−12(modp)(p-1)! \equiv n^{\frac{p-1}{2}} (mod \text{ } p)(p−1)!≡n2p−1(mod p)。由威尔逊定理,(p−1)!≡−1(modp)(p-1)! \equiv -1 (mod \text{ } p)(p−1)!≡−1(mod p)
- p∣np \mid np∣n,显然成立
引理
共有p−12\frac {p-1}{2}2p−1个nnn是ppp下的二次剩余
证明:设两个数u,vu,vu,v满足u≠v,u2≡v2(modp)u \neq v, u^2 \equiv v^2 (mod \text{ } p)u=v,u2≡v2(mod p),即p∣u2−v2=(u+v)(u−v)p \mid u^2-v^2=(u+v)(u-v)p∣u2−v2=(u+v)(u−v)
故p∣u+vp \mid u+vp∣u+v,反之同理
说人话:有且仅有模意义下的相反数平方相同。
所以x2x^2x2共有p−12\frac {p-1}{2}2p−1个不同取值
所以随机出一个(非)二次剩余是O(1)O(1)O(1)的
引理
(a+b)p≡ap+bp(modp)(a+b)^p\equiv a^p+b^p (mod \text{ } p)(a+b)p≡ap+bp(mod p)
二项式定理展开即可,不再证明。
重头戏开始。坐稳了,前方高能!
随机一数aaa,若(a2−np)=−1(\frac{a^2-n}{p})=-1(pa2−n)=−1,令w=a2−nw=\sqrt{a^2-n}w=a2−n,则(a+w)p+12(a+w)^\frac{p+1}{2}(a+w)2p+1是方程x2≡n(modp)x^2 \equiv n (mod \text{ } p)x2≡n(mod p)的根
什么?a2−na^2-na2−n不是非二次剩余吗?怎么开根?
没办法啊……那就类比虚数,定义z=a+bwz=a+bwz=a+bw吧(绝对不是人想出来的)
引理
wp≡−w(modp)w^p\equiv -w(mod \text{ } p)wp≡−w(mod p)
证明:wp≡wp−1w≡(a2−n)p−12w≡(a2−np)w≡−w(modp)w^p \equiv w^{p-1}w \equiv (a^2-n)^\frac{p-1}{2} w\equiv (\frac{a^2-n}{p})w\equiv -w (mod \text{ }p)wp≡wp−1w≡(a2−n)2p−1w≡(pa2−n)w≡−w(mod p)
最后证明:
x2≡(a+w)p+1≡(a+w)p(a+w)≡(ap+wp)(a+w)=(a−w)(a+w)=a2−w2≡n(modp)x^2\equiv (a+w)^{p+1}\equiv (a+w)^p(a+w) \equiv (a^p+w^p)(a+w)=(a-w)(a+w)=a^2-w^2\equiv n(mod \text{ } p)x2≡(a+w)p+1≡(a+w)p(a+w)≡(ap+wp)(a+w)=(a−w)(a+w)=a2−w2≡n(mod p)
证毕……
哎等等?xxx是虚数怎么办?
假设xxx是虚数,设x=a+bwx=a+bwx=a+bw(不是上面的aaa) 由xxx定义,a≠0a \neq 0a=0
x2=a2+b2w2+2abw=a2+a2b2−nb2+2abwx^2=a^2+b^2w^2+2abw=a^2+a^2b^2-nb^2+2abwx2=a2+b2w2+2abw=a2+a2b2−nb2+2abw是虚数,而nnn是实数。
假设不成立,所以xxx是实数(并不严谨)
例题:Timus OJ1132 Square Root
数据很小,暴力能过。但只有这题,将就了吧。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <cstdlib>
#define int long long
using namespace std;
struct complex{int x,y;};
int n,w2,p;
inline complex operator *(const complex& a,const complex& b){return (complex){(a.x*b.x%p+a.y*b.y%p*w2%p)%p,(a.x*b.y%p+a.y*b.x%p)%p};}
inline complex qpow(complex a,int b)
{complex ans;ans.x=1,ans.y=0;while (b){if (b&1) ans=ans*a;a=a*a,b>>=1;}return ans;
}
inline int qpow(int a,int b)
{int ans=1;while (b){if (b&1) ans=ans*a%p;a=a*a%p,b>>=1;}return ans;
}
inline int lerender(const int& x){return qpow(x,p>>1);}
int solve()
{if (lerender(n)==p-1) return -1;int a;while (true){a=rand()%p;w2=(a*a-n)%p;if (w2<0) w2+=p;if (lerender(w2)==p-1) break;}complex res;res.x=a,res.y=1;res=qpow(res,(p+1)>>1);return res.x;
}
inline int read()
{int ans=0;char c=getchar();while (!isdigit(c)) c=getchar();while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();return ans;
}
void write(const int& x)
{if (x<10) return(void)putchar(x^48);int t=x/10;write(t),putchar((x-(t<<1)-(t<<3))^48);
}
main()
{int T=read();while (T--){n=read(),p=read();if (p==2) {printf("1\n");continue;}int ans=solve();if (ans==-1) printf("No root\n");else{int ans2=p-ans;if (ans>ans2) swap(ans,ans2);write(ans),putchar(' '),write(ans2),putchar('\n');}}return 0;
}