这个 PPT 里面前半部分内容更完善
由于时间紧迫,本文不会太详细,并且由于 OI 不重视证明,本文很多时候可能只给出证明链接。
定理太多证明太难怎么办?证什么,背结论即可
欧几里得算法
小学数学,直接跳过
gcd(a,b)=gcd(b,amodb)
扩展欧几里得算法
即 exgcd,常用于求 ax+by=gcd(a,b) 的一组可行解。
首先,当 a,b 不全为 0,ax+by=gcd(a,b) 一定有解,证明见后面的裴蜀定理。
当 b 等于 0 时,gcd(a,b)=a,此时 x=1,y=0,显然是方程的一组特解。
而当 b 不等于 0 时,由 gcd(a,b)=gcd(b,amodb) 可以知道:ax+by=bx′+(amodb)y′,而:
bx′+(amodb)y=bx′+(a−⌊ba⌋×b)y′=ay′+bx′−⌊ba⌋×by′=ay′+b(x′−⌊ba⌋y′)
由 ax+by=ay′+b(x′−⌊ba⌋y′) 可以得到:x=y′,y=x′−⌊ba⌋y′。
将 x′,y′ 不断代入递归求解直至 gcd 为 0 再递归 x=1,y=0 回去求解即可。
代码
1 2 3 4 5 6 7 8 9 10 11
| void exgcd(int a,int b,int &x,int &y) { if(!b) { x=1,y=0; return; } exgcd(b,a%b,x,y); int tmp=x; x=y,y=tmp-(a/b)*y; }
|
例题
P5656 【模板】二元一次不定方程 (exgcd)
对于形如 ax+by=c 的二元一次方程,显然当且仅当 gcd(a,b)∣c 时,存在整数解。
设 d=gcd(a,b),a′=a/d,b′=b/d,c′=c/d,此时 gcd(a′,b′)=1,我们只需要求出 a′x′+b′y′=1 的一组特解 x0′,y0′,原方程的其中一组解便是 x0=x0′×c,y0=y0′×c。
求出特解后,该方程的通解便是:x=x0+b′t,y=y0+a′t,其中 t 是任意整数。
数论分块
如果你想要看严谨证明:https://www.luogu.com.cn/blog/Rong7/post-bi-ji-shuo-lun-fen-kuai
数论分块主要用于在 O(n) 的时间复杂度下求形如 ∑i=1n⌊in⌋ 的式子。
直接打表找规律,设 n=10:
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|
⌊in⌋ | 10 | 5 | 3 | 2 | 2 | 1 | 1 | 1 | 1 | 1 |
可以发现 ⌊in⌋ 分别在一定的区域相等,因此只需要枚举每一个块的左边界 l 和右边界 r 即可。
仍然找规律,可以发现 r=⌊⌊in⌋n⌋,有了有边界,每一个块的和就可以计算了。
代码
1 2 3 4 5
| for(int l=1,r;l<=n;l=r+1) { r=n/(n/l); ans+=1ll*(r-l+1)*(n/l); }
|
例题
UVA11526 H(n)
纯板子,跳过
P2261 【CQOI2007】余数求和
i=1∑nkmodi=i=1∑nk−⌊ik⌋×i=i=1∑nk−i=1∑n⌊ik⌋×i=n×k−i=1∑n⌊ik⌋×i
后面的就是板子了。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include<bits/stdc++.h> using namespace std; typedef long long ll; ll n,k,ans; int main() { ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n>>k; ans=n*k; for(int l=1,r;l<=n;l=r+1) { if(k/l==0) r=n; else r=min(n,k/(k/l)); ans-=(r-l+1)*(k/l)*(l+r)/2; } cout<<ans<<endl; return 0; }
|
费马小定理
定义有两种等价的形式:
- 若 p 为质数,gcd(a,p)=1,则 ap−1≡1(modp)
- 对于任意整数 a,ap≡a(modp)
这玩意似乎只是用来求逆元和进行素性测试的,个人感觉直接背下来也没什么问题。
归纳法证明
显然 1p≡1(modp) 成立,我们假设 ap≡a(modp) 成立,那么:
(a+1)p=ap+(1p)ap−1+(2p)ap−2+⋯+(p−1p)a+1
而由组合数的定义,我们知道 ∀1≤k≤p−1,(kp)≡0(modp) 。
那么 (a+1)p≡ap+1(modp),将 ap≡a(modp) 代入即可得到 (a+1)p≡a+1(modp)。
另外一种证明:https://oi-wiki.org/math/number-theory/fermat/#证明
欧拉函数
表示小于等于 n 和 n 互质的数的个数。写作 φ(n)。
比如:φ(1)=1,φ(7)=6,φ(12)=4
下面是它的一些性质:
当 n 是质数时,φ(n)=n−1
欧拉函数是积性函数,即若有 gcd(a,b)=1,φ(a×b)=φ(a)×φ(b)
由唯一分解定理,设 n=∏i=1spiki,其中 pi 是质数,有 φ(n)=n×∏i=1spipi−1
证明:
φ(n)=i=1∏sφ(piki)=i=1∏s(pi−1)×piki−1=i=1∏spiki×(1−pi1)=ni=1∏s(1−pi1)
欧拉定理
若 gcd(a,m)=1,则 aφ(m)≡1(modm)
证明:https://oi-wiki.org/math/number-theory/fermat/#证明_1
扩展欧拉定理
ab≡{ab,b<φ(m)abmodφ(m)+φ(m),b≥φ(m)(modm)
证明:https://oi-wiki.org/math/number-theory/fermat/#证明_2
欧拉定理和扩展欧拉定理似乎只能用来降幂,所以说直接背也没太大问题。
例题
P5091 【模板】扩展欧拉定理
套公式即可。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include<bits/stdc++.h> using namespace std; typedef long long ll; ll a,mod,b; bool flag=false; ll qpow(ll x,ll y) { ll res=1; while(y) { if(y&1) res=res*x%mod; x=x*x%mod; y>>=1; } return res; } inline ll phi(ll x) { ll ans=x; for(ll i=2;i*i<=x;i++) { if(x%i==0) { ans=ans/i*(i-1); while(x%i==0) x/=i; } } if(x>1) ans=ans/x*(x-1); return ans; } int main() { cin>>a>>mod; ll tmp=phi(mod); char ch=getchar(); while(!isdigit(ch)) ch=getchar(); while(isdigit(ch)) { b=b*10+ch-48,ch=getchar(); if(b>tmp) b%=tmp,flag=1; } if(flag) b+=tmp; cout<<qpow(a,b)<<endl; return 0; }
|
乘法逆元
如果一个线性同余方程 ax≡1(modb),则 x 被称作 amodb 的逆元,记作 a−1
乘法逆元常用来解决有理数取模的问题。
例题
P2613 【模板】有理数取余
这道题就是求 b 的逆元。
下面是几种求逆元的方法:
扩展欧几里得算法
ax≡1(modb) 与 ax+by=1 是等价的,因此就是求 x 的最小正整数解。
注意需满足 gcd(a,b)=1
快速幂法
x≡ab−2(modb) ,用快速幂求即可。证明如下:
因为 ax≡1(modb),根据费马小定理,ax≡ab−1(modb),所以 x≡ab−2(modb)
注意需满足 b 是质数。
线性求逆元
线性复杂度求出 1,2,⋯,n 中每个数关于 p 的逆元。
首先,1 的逆元是它本身,即 1−1≡1(modp)
对于递归情况 i−1,我们设 k=⌊ip⌋,j=pmodi,有 p=ki+j,那么:
ki+j≡0(modp)
在两边同时乘 i−1×j−1:
kj−1+i−1≡0(modp)i−1≡−kj−1(modp)i−1≡−⌊ip⌋(pmodi)−1(modp)
而 pmodi<i ,因此我们可以从已经计算出的答案得到 i−1。
代码如下:1 2 3
| inv[1]=1; for(int i=2;i<=n;i++) inv[i]=1ll*(p-p/i)*inv[p%i]%p;
|
例题:【模板】乘法逆元
线性求任意 n 个数的逆元
线性复杂度求出任意给定 n 个数(1≤ai<p)的逆元。
首先计算 n 个数的前缀积,记为 si,计算出 sn 的逆元,记为 svn。
由于 svn 是前 n 个数的逆元,我们将它乘上 an 后,它就会和 an 的逆元抵消,从而得到前 n−1 个数的逆元 svn−1,以此类推,可以计算出所有 svi,ai 的逆元即为 svi×si−1。1 2 3 4 5
| s[0]=1; for(int i=1;i<=n;i++) s[i]=s[i-1]*a[i]%p; inv[n]=qpow(s[n],p-2); for(int i=n;i>=1;i--) inv[i-1]=inv[i]*a[i]%p; for(int i=1;i<=n;i++) inv[i]=inv[i]*s[i-1]%p;
|
例题:【模板】乘法逆元 2
中国剩余定理
语文不好不知道怎么描述,所以咕咕咕。建议看 OI Wiki,写得挺清楚的。
中国剩余定理 - OI Wiki
再给 xhr 的博客打个广告:CRT,中国剩余定理&P1495题解
扩展中国剩余定理
咕咕咕,之后来填坑
威尔逊定理
定义:对于质数 p 有 (p−1)!≡−1(modp)。
直接背!
证明
首先对于 p≤5 的情况,直接证即可。
对于 p≥5,则 (p−1)!=1×(p−1)×(2×3×⋯×(p−2))
我们假设 2 到 p−2 中有两个数 a,b 在模 p 意义下有相同的逆元 t,则:
at≡bt≡1(modp)
可以得到,p∣t(a−b)。
又因为 a,b,t<p ,故而 t∤p 且 p∤(a−b),因此 p∤t(a−b),矛盾。
故 2 到 p−2 中的每个数逆元不同。
假设 2≤k≤(p−2) 且 k 的逆元是它本身,则:
k2≡1(modp)
即 p∣k2−1,因式分解,p∣(k+1)(k−1),解得 k=1 或 k=p−1 ,不在其范围中,故不存在 k。
综上,2 到 p−2 中每个数在模 p 意义下逆元不同,且不为自己,同时也都在 2 到 p−2 中,所以:
(p−1)!≡1×(p−1)×(2×3×⋯×(p−2))≡p−1≡−1(modp)
证毕。
例题
UVA1434 YAPTCHA
这道题虽然是紫题,但是很水。
原根
原根,启动!
详细证明请参考 原根 - OI Wiki
阶
若满足同余式 an≡1(modm) 的最小正整数存在,则这个 n 被称作 a 模 m 的阶,记作 δm(a)。
阶有一个下面会用到的性质:
若 gcd(a,n)=1,且 ak≡1(modn),则 k∣φ(n)
定义
正整数 g 是正整数 n 的原根,当且仅当 1≤g≤n−1,且 g 模 n 的阶为 φ(n)。
什么数有原根
只有 2,4,pk,2×pk(p 为奇素数)这类数才有原根。
如何求 n 的所有原根
先找到 n 的最小原根 g,那么 n 的每一个原根都形如 gk 的形式,其中 k 满足 gcd(k,φ(n))=1,于是我们还可以知道其原根个数为 φ(φ(n))。
找最小原根
据说 n 的最小原根不超过 n0.25 ,因此我们可以直接枚举 g,具体方法如下:
根据定义,若 g 是 n 的原根,需要满足两个条件:
gφ(n)≡1(modn)
∀1≤k<φ(n),gk≡1(modn)
问题在于第二点,我们不可能把所有小于 φ(n) 的数都枚举一遍。
这时候就会用到阶的那个性质,也就是说我们只需要检验 φ(n) 的因子即可。进一步的优化,设 n 的所有素因数为 p1,p2,⋯,pt,只需检验所有的 piφ(n),因为它们涵盖了 φ(n) 的所有因子的倍数。
细节见代码:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
| #include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=1e6;
int prime[maxn+5],tot; bool vis[maxn+5]; inline void pri() { for(int i=2;i<=maxn;i++) { if(!vis[i]) prime[++tot]=i; for(int j=1;j<=tot;j++) { if(1ll*prime[j]*i>maxn) break; vis[prime[j]*i]=1; if(i%prime[j]==0) break; } } }
bool is[maxn+5]; inline void init() { is[2]=is[4]=1; for(int i=2;i<=tot;i++) { ll x=1; for(int j=1;;j++) { x*=prime[i]; if(x>maxn) break; is[x]=1; if(2*x<=maxn) is[2*x]=1; } } }
inline ll qpow(ll x,ll y,ll mod) { ll res=1; while(y) { if(y&1) res=res*x%mod; x=x*x%mod; y>>=1; } return res; }
inline int phi(int x) { int res=x; for(int i=2;i*i<=x;i++) { if(x%i==0) { res=res/i*(i-1); while(x%i==0) x/=i; } } if(x>1) res=res/x*(x-1); return res; }
int factor[maxn+5],num; inline void divide(int x) { num=0; for(int i=2;i*i<=x;i++) { if(x%i==0) { factor[++num]=i; if(i!=x/i) factor[++num]=x/i; } } if(x>1) factor[++num]=x; }
inline bool check(int a,int phin,int mod) { for(int i=1;i<=num;i++) { if(qpow(a,phin/factor[i],mod)==1) return false; } return true; }
int t,n,d,ans[maxn+5],cnt;
int main() { ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); pri(); init(); cin>>t; while(t--) { cnt=0; cin>>n>>d; if(!is[n]) { cout<<"0\n\n"; continue; } int phin=phi(n),g; divide(phin); for(int i=1;i<n;i++) { if(qpow(i,phin,n)==1&&check(i,phin,n)) { g=i; break; } } int x=1; for(int i=1;i<=phin;i++) { x=1ll*x*g%n; if(__gcd(i,phin)==1) ans[++cnt]=x; } sort(ans+1,ans+1+cnt); cout<<cnt<<"\n"; for(int i=d;i<=cnt;i+=d) cout<<ans[i]<<" "; cout<<"\n"; } return 0; }
|
代码可以过掉 P6091 【模板】原根
大步小步算法
英文名:Baby Step Giant Step,缩写 BSGS,别名拔山盖世
BSGS 是一种用来解决形如:
ax≡b(modm)
的高次同余方程的算法,时间复杂度 O(n),其中 a 与 m 要求互质。
设 t=⌊p⌋,0≤j≤t−1,那么 x 可以被表示为 x=i×t−j,可得 ai×t−j≡b(modm),同乘 aj 得:
ai×t≡b×aj(modp)
我们考虑枚举 0≤j≤t−1,将所有 b×aj 插入一个哈希表中,再枚举 0≤i≤t ,查询哈希表中是否有和 ai×t 相等的值,如果有,就得出了一组 i,j,由于 t 已知,就可以求出方程的解 x。
感觉相当暴力
例题
P3846 【模板】BSGS
板子,按上面的来就对了。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include<bits/stdc++.h> using namespace std; typedef long long ll; ll qpow(ll a,ll n,ll mod) { ll res=1; while(n) { if(n&1) res=res*a%mod; a=a*a%mod; n>>=1; } return res; } ll p,b,n;
map<ll,ll> hmap; inline ll bsgs() { ll t=sqrt(p); for(int i=0;i<t;i++) hmap[n*qpow(b,i,p)%p]=i; b=qpow(b,t,p); if(b==0) return n==0?1:-1; for(int i=0;i<=t;i++) { ll tmp=qpow(b,i,p); if(hmap[tmp]&&i*t-hmap[tmp]>=0) return i*t-hmap[tmp]; } return -1; } int main() { ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>p>>b>>n; ll tmp=bsgs(); if(tmp==-1) cout<<"no solution\n"; else cout<<tmp<<endl; return 0; }
|
P2485 【SDOI2011】 计算器
欧拉公式
eiθ=cosθ+isinθ
当取 θ=π 时,有
eiπ=cosπ+isinπ=1
单位根
前置知识:复数
在复数域下,满足 xn=1 的 x 被称为 n 次单位根。
根据代数基本定理(n 次复系数多项式方程在复数域内有且只有 n 个根),n 次单位根一共有 n 个。
容易发现,这 n 个单位根在恰好 n 等分了复平面上的单位元,并且将其按照辐角大小排序后第 k 大的根是 ein2kπ
本原单位根
0 到 n−1 次方的值能生成所有 n 次单位根的 n 次单位根称为 n 次本原单位根。
显然 x1=ein2π 是一个本原单位根。
n 次本原单位根记为 ωn(这个是希腊字母 Omega)。
性质
以下性质会在 FFT 中用到。
设 n 是一个正偶数,且 n=2m,有:
(ωnk)2=ωmkωnk+m=−ωnk
未完待续
咕咕咕