2025.3.1数学听课笔记
质数
一到 \(n\) 以内约有 \(\frac{n}{\log n}\) 个质数
可以在 \(\sqrt n\) 的时间下判断一个数是否为质数
\(\sum_{i=1}^n\frac{1}{i} ≈ \ln n\)
质数筛法:埃氏筛和欧拉筛
埃氏筛:从小到大枚举,将他所有的非平凡倍数标记为“非质数”。
欧拉筛:考虑让每个数x的最小质因子 \(p\) ,让 \(x\) 被 \((\frac{_x}{^p})\) 通过 \(\times p\) 筛掉。
具体做法:记录每个数 \(x\) 的最小质因子 \(p\) ,枚举每个每一个比 \(p\) 小的最小质因子\(e_i\),筛去 \(e_i\times x\)
代码:
e[i] //下标为最小质因子 e[i]为i是第几个质数
prime[i] //质数
for(int i=2;i<=n;i++){
if(!e[i]) prime[e[i]=++tot]=i;
for(int j=1;j<=e[i] && prime[j]*i<=n;j++){
e[prime[j]*i]=j;
}
}
质因数分解
\(\sqrt n\) 暴力分解
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=1e5+50;
int ans[N],cnt[N],tot=1;
bool f=false;
int main(){
int n,x;
cin>>n;
x=n;
for(int i=2;x!=1;i++){
f=false;
while(x%i==0){
x/=i;
cnt[tot]++;
ans[tot]=i;
f=true;
}
if(x==1) break;
if(f) tot++;
}
for(int i=1;i<=tot;i++){
cout<<ans[i]<<' '<<cnt[i]<<'\n';
}
return 0;
}
\(O(V)\) 预处理,\(O(\log n)\)分解
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=1e5+50;
const int V=1e7;
int e[V+50],prime[N],cnt[N],idx,idx2=1;
int ans[N];
int n,tot;
bool f=false;
int main(){
cin>>n;
for(int i=2;i<=n;i++){
if(!e[i]) prime[e[i]=++tot]=i;
for(int j=1;j<=e[i] && prime[j]*i<=n;j++){
e[prime[j]*i]=j;
}
}
int nn=n;
while(nn!=1){
idx++;
f=false;
while(nn%prime[idx]==0){
nn/=prime[idx];
cnt[idx2]++;
ans[idx2]=prime[idx];
f=true;
}
if(nn==1) break;
if(f) idx2++;
}
for(int i=1;i<=idx2;i++){
cout<<ans[i]<<' '<<cnt[i]<<'\n';
}
return 0;
}
约数(因数)
约数个数:质因子分解后,(指数+1)的乘积。
即$$\prod_{i=1}^{s}(α_i+1)$$
约数和:质因子分解后,\(1+p+p^2 + …… +p^{a_i}\) 的乘积
即$$\prod_{i=1}^{s} (\sum_{j=0}^{α_i} p_i^j)$$
通过质因数分解求因数:写一个 dfs ,枚举每一个质因子的指数。即可求解出该数的所有因子。
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=1e5+50;
const int V=1e7;
int e[V+50],prime[N],cnt[N],idx,idx2=1;
int ans[N];
bool fac[V];
int n,tot;
bool f=false;
void dfs(int x,int d){
if(d>idx2+1) return ;
fac[x]=1;
for(int i=1;i<=cnt[d];i++){
dfs(x*pow(ans[d],i),d+1);
}
return ;
}
int main(){
cin>>n;
for(int i=2;i<=n;i++){
if(!e[i]) prime[e[i]=++tot]=i;
for(int j=1;j<=e[i] && prime[j]*i<=n;j++){
e[prime[j]*i]=j;
}
}
int nn=n;
while(nn!=1){
idx++;
f=false;
while(nn%prime[idx]==0){
nn/=prime[idx];
cnt[idx2]++;
ans[idx2]=prime[idx];
f=true;
}
if(nn==1) break;
if(f) idx2++;
}
for(int i=1;i<=idx2;i++){
cout<<ans[i]<<' '<<cnt[i]<<'\n';
}cout<<'\n';
for(int i=1;i<=idx2;i++){
dfs(1,i);
}
for(int i=1;i<=n;i++){
if(fac[i]){
cout<<i<<' ';
}
}
return 0;
}
\(gcd\) & \(lcm\)
\(gcd\):最大公因子
\(lcm\):最小公倍数
\(lcm(x,y) = \frac{x\times y}{gcd(x,y)}\)
\(x = g*a,y = g*b,gcd(x,y)=g,lcm(x,y) = g\times a\times b\)。条件\(gcd(a,b) = 1\)
在质因子分解下为指数上的取 \(\min\) 和 \(\max\)
\(gcd\) 求法:辗转相除
\(gcd(x,y) = gcd(x-y,y) -> gcd(x,y) = gcd(x \% y,y)\)
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
int Gcd(int x,int y){
while(y!=0){
int tmp=x%y;
x=y;
y=tmp;
}
return x;
}
int main(){
int x,y;
cin>>x>>y;
cout<<Gcd(x,y);
return 0;
}
欧拉函数
互质:两个数的最大公因数为 \(1\)
欧拉函数:\(\varphi(n) = 1……n\) 中与 \(n\) 互质的个数
\(\varphi(n) = (p_1-1)p{_1}^{a{_1}-1} \times (p_2-1)p{_2}^{a{_2}-1}\times …… \times (p_k-1)p{_k}^{a{_k}-1}\)
即$$\varphi(n)=n\times \prod_{i=1}^{s} \frac{p_i-1}{p_i}$$
当 \(n\) 分解为 \(a\) , \(b\) 且 \(a\) 与 \(b\) 互质时,\(f(n)=f(a)\times f(b)\) 此时称 \(f(x)\) 为积性函数
\(\varphi(n)\) 为积性函数
下面是 \(\varphi(n)\) 的求解代码,用于求单个值的欧拉函数,时间复杂度应为 \(O(\sqrt n)\):
//求单个值的欧拉函数
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
int phi(int n){
int ans=n;
for(int i=2;i*i<=n;i++){
if(n%i==0){
ans=ans/i*(i-1);
while(n%i==0) n/=i;
}
}
if(n>1) ans=ans/n*(n-1);
return ans;
}
int main(){
int n;
cin>>n;
cout<<phi(n);
return 0;
}
这个是线性求 \(n\) 个值的欧拉函数,时间复杂度是 \(O(n)\) :
int phi[N];
bool prime[N];
vector<int> pri;
void varphi(){
phi[1]=1;
for(int i=2;i<=n;i++){
if(!prime[i]){
pri.push_back(i);
phi[i]=i-1;
}
for(int k=0;k<(int)pri.size();k++){
int j=pri[k];
if(i*j>n) break;
prime[i*j]=1;
if(i%j==0){
phi[i*j]=phi[i]*j;
break;
}
phi[i*j]=phi[i]*phi[j];
}
}
}
同余
-在模 \(p\) 的意义下,\(a\) 模等于 \(b\) 当且仅当 \(p\) 整除\((a - b) -> (a-b)\%p=0\)
同余的性质
对于整数 \(a\) ,\(b\) ,\(c\) 和自然数\(m\) ,\(n\) ,对模\(m\)同余满足:
1.自反性:\(a\%m=a\%m\)
2.对称性:若\(a\%m=b\%m\),则\(b\%m=a\%m\)
3.传递性:若\(a\%m=c\%m\),\(b\%m=c\%m\),则\(a\%m=b\%m\)
4.同加性:若\(a\%m=b\%m\) 则\((a+c)\%m=(b+c)\%m\)
5.同乘性:若\(a\%m=b\%m\) 则\((a\times c)\%m=(b\times c)\%m\)
6.同幂性:若\(a\%m=b\%m\) 则\(a^c=b^c \pmod m\)
!同余没有可除性!
欧拉定理
若正整数 \(a\) ,\(n\) 互质,则 \(a{^{\varphi(n)} =1 \pmod n}\) ,模数 \(n\) 可以不为质数,但需要与 \(a\) 互质。
广义欧拉定理
对于所有 \(a\) , \(m\) ,若 \(\gcd(a,m)\) 不等于 \(1\) ,则有
【模板】扩展欧拉定理
模板题,套上述公式即可。什么你想知道超级大数 \(n\) 怎么取模 \(\varphi(m)\) ?我也不会啊。
介绍一下现学的大整数取模:
对于一个数 \(n\) 我们令 \(n = n_1\times 10{^{len-1}} + n_2 \times 10{^{len-2}} …… n_{len-1} \times 10^1 + n_{len} \times 1\) 。说人话就是把它拆成每一位数,对于同余,我们有同乘性和同加性,所以我们只需要对每一位数取模,再对他们的和取模就可以啦!
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
#define int long long
using namespace std;
int a,mo;
int phi(int n){
int ans=n;
for(int i=2;i*i<=n;i++){
if(n%i==0){
ans=ans/i*(i-1);
while(n%i==0) n/=i;
}
}
if(n>1) ans=ans/n*(n-1);
return ans;
}
int qpow(int a,int b){
int ans=1,k=a;
while(b){
if(b&1) ans=ans*k%mo;
b=b>>1;
k=k*k%mo;
}
return ans%mo;
}
int qread(int mo){
char c=getchar();
int x=0;
bool f=false;
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9'){
// 高级快读,(x<<3)+(x<<1) 相当于 x*=10,(c^'0') 相当于 c-='0'
x=(x<<3)+(x<<1)+(c^'0');
if(x>=mo){
x%=mo,f=true;
}
c=getchar();
}
if(f) return x+mo;
else return x;
}
signed main(){
cin>>a>>mo;
int b=phi(mo);
int c=qread(b);
cout<<qpow(a,c);
return 0;
}
裴蜀定理
形如 \(a\times x + b\times y = c (x∈N^*,y∈N^*)\) 有解的充要条件是 \(\gcd(a,b)|c\) 也就是 \(c\% \gcd(a,b)=0\)
【模板】裴蜀定理
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
int gcd(int x,int y){
return y ? gcd(y,x%y) : x;
// 题解区最最最优美的 gcd 求法
}
int n,ans;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int a;cin>>a;
a= a>=0 ? a : -a;
ans=gcd(ans,a);
}
cout<<ans;
return 0;
}
逆元
\(\gcd(A,p)=1 , A\times x = 1 \pmod p\), \(x\) 则为逆元,逆元是唯一的。
费马小定理,扩展欧几里得(EXgcd)。
下面是扩展欧几里得(exgcd)
\(a\times x+b\times y=1\)
\(b\times x'+(a\%b)\times y'=1\)
\(b\times x'+(a-(a/b)\times b)\times y'=1\)
\(a\times y'+b\times (x'-(a/b)\times y')=1\)
\(x=y',y=x'-(a/b)\times y'\)
发现上式在 \(y'=0\) 时必然有一组解为 \(x=1\) 。递归去做这件事。
解出来的 \(x\) 即为逆元。
同余方程
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
long long x,y;
void exgcd(long long a,long long b){
if(b==0){
x=1;
y=0;
return ;
}
exgcd(b,a%b);
long long t=x;
x=y;
y=t-a/b*y;
return ;
}
int main(){
long long a,b;
cin>>a>>b;
exgcd(a,b);
x=(x%b+b)%b;
cout<<x;
return 0;
}
中国剩余定理(EXCRT)
给定 \(n\) 组非负整数 \(a_i\) , \(b_i\) ,求解关于 \(x\) 的方程组的最小非负整数解
\(x=b_1 \pmod {a_1}\)
\(x=b_2 \pmod {a_2}\)
\(……\)
\(x=b_n \pmod {a_n}\)
【模板】中国剩余定理(CRT)/ 曹冲养猪
如果所有的 \(a_i\) 两两互质,那么可以使用正常的 CRT 求解。
具体详见第一篇题解
#include<bits/stdc++.h>
#define int __int128
using namespace std;
const int N=15;
int read(){
bool f=false;
int x=0;
char c=getchar();
if(c<'0' || c>'9'){if(c=='-') f=true;c=getchar();}
while(c>='0' && c<='9'){
x=(x<<3)+(x<<1)+(c-'0');
c=getchar();
}
return f==0 ? x : -x;
}
void print(int ans){
if(ans==0) cout<<0;
else{
string s;
while(ans){
s+=ans%10+'0';
ans/=10;
}
for(int i=s.length()-1;i>=0;i--){
cout<<s[i];
}
}
return ;
}
int exgcd(int a,int b,int &x,int &y){
if(!b){
x=1;
y=0;
return a;
}
int res=exgcd(b,a%b,x,y);
int tmp=x;
x=y;
y=tmp-a/b*y;
return res;
}
int n;
int a[N],b[N];
int ans=1,res;
signed main(){
n=read();
for(int i=1;i<=n;i++){
a[i]=read();
b[i]=read();
ans*=a[i];
}
for(int i=1;i<=n;i++){
int k=ans/a[i];
int x,y;
int gcd=exgcd(k,a[i],x,y);
res=res+k*b[i]*x%ans;
}
print((res%ans+ans)%ans);
return 0;
}
【模板】扩展中国剩余定理(EXCRT)
解法:
\(x=k_1\times a_1+b_1\)
\(x=k_2\times a_2+b_2\)
\(k_1\times a_1-k_2\times a_2 = b_2 - b_1\)
已知 \(a_1 , b_1 , a_2 , b_2\) 求解 \(k_1,k_2\)。
有想法了吗?是上述的 exgcd 对吧。对于原方程组做若干次 exgcd 就有解了。(证明请移步题解区第一篇,以及还有一些重要观察。)
本题还需要一个龟速承,原理是把一个十进制数转成二进制数去做,去凑出每一位的数,可以看看代码理解一下。
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
#define int long long
using namespace std;
const int N=1e5+10;
int aa[N],bb[N];
int n;
int read(){
int x=0;bool f=false;
char c=getchar();
while(c<'0' || c>'9') {if(c=='-') f=1;c=getchar();}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^'0');c=getchar();}
return f==0?x:-x;
}
int smul(int a,int b,int mo){
int res=0;
while(b>0){
if(b&1) res=(res%mo+a%mo)%mo;
a=(a%mo+a%mo)%mo;
b>>=1;
}
return res;
}
int exgcd(int a,int b,int &x,int &y){
if(b==0) {x=1;y=0;return a;}
int gcd=exgcd(b,a%b,x,y);
int tmp=x;
x=y;
y=tmp-a/b*y;
return gcd;
}
int excrt(){
int x,y;
int lcm=bb[1],ans=aa[1];
for(int i=2;i<=n;i++){
int a=lcm;
int b=bb[i];
int c=(aa[i]-ans%b+b)%b;
int gcd=exgcd(a,b,x,y);
int bgcd=b/gcd;
if(c%gcd!=0) return -1;
x=smul(x,c/gcd,bgcd);
ans+=x*lcm;
lcm*=bgcd;
ans=(ans%lcm+lcm)%lcm;
}
return (ans%lcm+lcm)%lcm;
}
signed main(){
n=read();
for(int i=1;i<=n;i++){
bb[i]=read();
aa[i]=read();
}
cout<<excrt();
return 0;
}
BSGS 北上广深算法(确信)
给定一个质数 \(p\) ,以及一个整数 \(a\) ,一个整数 \(b\) ,现在要求你计算一个最小的非负整数 \(l\) ,满足\(a^l=b \pmod p\)
[TJOI2007] 可爱的质数/【模板】BSGS
朴素做法枚举 \(l\) ,写出如下代码:
#include<iostream>
#define int long long
using namespace std;
int a,b,p;
int ans=1;
signed main(){
cin>>p>>a>>b;
for(int i=1;i<=(p<<1);i++){
ans=(ans*a)%p;
if(ans==b){
cout<<i;
return 0;
}
}
cout<<"no solution";
return 0;
}
收获如下好成绩
接下来就是 BSGS 算法了:分块(优雅的暴力)。
原式为:\(a^l = b \pmod p\),化为 \(a^{Am-n} = b \pmod p\),左右两侧同乘 \(a^n\),式子变为:\(a^{Am} = ba^n \pmod p\)
我们可以预处理出所有的 \(ba^n \bmod p\) 存入 hash 中,再去计算 \(a^{Am} \bmod p\) 然后去查 hash 表中的数是否存在相同的数。 其他的详见代码。欸嘿我不会 hash ,我用 map。
用 hash 的时间复杂度是 \(O(\sqrt p)\) 。用 map 是 \(O(\sqrt p \times \log p)\)
#include<iostream>
#include<map>
#include<cmath>
#define int long long
using namespace std;
map<int,int> mp;
int qpow(int a,int b,int p){
int ans=1;
while(b>0){
if(b&1) ans=(ans*a)%p;
a=(a*a)%p;
b>>=1;
}
return ans;
}
int BSGS(int a,int b,int p){
if(a%p==b%p) return 1;
if(a%p==0 && b!=0) return -1;
int len=(long long)sqrt(p)+1;
// 预先求出a^sqrt(p)
int tmp=qpow(a,len,p);
// 求解 b*a^(0->sqrt(p))
for(int i=0;i<=len;i++){
mp[b]=i;
b=(b*a)%p;
}
b=1;
// 求解 b*a^{ (0->sqrt(p))*sqrt(p) }
for(int i=1;i<=len;i++){
b=(b*tmp)%p;
if(mp[b]) return i*len-mp[b];
}
return -1;
}
int p,a,b;
signed main(){
cin>>p>>a>>b;
int ans=BSGS(a,b,p);
if(ans==-1) cout<<"no solution";
else cout<<ans;
return 0;
}
例题
AT_ACL_Contest_1 B-sum is multiple
求最小的 \(k\) 满足 \(k\times (k+1)\%(2\times n) = 0\)
跟数竞生一顿商讨后,他们告诉我要构造CRT,那就试试吧。
令 \(a\times b=2\times n\) 此时可得出
令 \(k=a\times x\),代入上式化简得到:
枚举 \(a,b\) ,直接上 exgcd 求解 \(x\) 。
注意开 __int128。
#include<iostream>
#define int long long
#define ll __int128
using namespace std;
int n;
int ans;
ll exgcd(ll a,ll b,ll &x,ll &y){
if(b==0){
x=1;
y=0;
return a;
}
ll res=exgcd(b,a%b,x,y);
ll tmp=x;
x=y;
y=tmp-a/b*y;
return res;
}
ll ask(ll a,ll b){
ll x=0,y=0;
ll gcd=exgcd(a,b,x,y);
if((b-1)%gcd!=0){
return 1e18;
}
x=(x%b*(b-1)%b+b)%b;
if(!x){
x+=b;
}
if(a*x>ans){
return 1e18;
}
return a*x;
}
signed main(){
cin>>n;
n*=2;
ans=n-1;
for(int i=1;1ll*i*i<=n;i++){
if(n%i==0){
ans=min((ll)ans,min(ask(i,n/i),ask(n/i,i)));
}
}
cout<<ans;
return 0;
}
沙拉公主的疑惑
给定\(m\le n\),求 \([1,n!]\) 中有多少数与 \(m!\) 互质,答案对质数 \(p\) 取模,\(n\le 1e7\),\(1e4\)组测试数据。
咕着
多少个1?
若干个 \(1……1111 (N)\) 的形式,思考一下可以转化为 \(\frac{10^n-1}{9}\) 的形式。
那么原题就变为求:
略微化简,得到如下式子:
求最小整数 \(n\) 满足上述式子,套BSGS板子。
中间需要开 __int128 ,或者使用快速乘。
原理是乘法分配律,可以试着推一把(反正我没推)。
#include<bits/stdc++.h>
#define int long long
#define ll long long
using namespace std;
unordered_map<int,int> mp;
int mul(int a,int b,int p){
int l=a*(b>>25ll)%p*(1ll<<25)%p;
int r=a*(b&((1ll<<25)-1))%p;
return (l+r)%p;
}
int qpow(int a,int b,int p){
int res=1;
while(b){
if(b&1) res=mul(res,a,p);
a=mul(a,a,p);
b>>=1;
}
return res;
}
int BSGS(int a,int b,int p){
int len=ceil(sqrt(p));
for(int i=0;i<len;i++) mp[mul(b,qpow(a,i,p),p)]=i;
int tmp=qpow(a,len,p);
for(int i=0;i<=len;i++){
int x=qpow(tmp,i,p);
int k=mp.count(x) ? mp[x] : -1;
if(k>=0 && i*len-k>=0) return i*len-k;
}
return -1;
}
int n,m;
signed main(){
cin>>n>>m;
n=n*9+1;
n%=m;
cout<<BSGS(10,n,m);
return 0;
}
仪仗队
可观测的坐标需要 \((x,y)\) 互质,直接求小于 \(x\) 的质因子个数 \(\varphi(x)\) 最后答案:\(\sum_{i=1}^{n-1} \varphi(i)+1\)
需要线性求出 \(1-n\) 的欧拉函数。
#include<iostream>
#include<vector>
using namespace std;
const int N=40050;
int n;
long long ans=0;
int phi[N];
bool prime[N];
vector<int> pri;
void varphi(){
phi[1]=1;
for(int i=2;i<=n;i++){
if(!prime[i]){
pri.push_back(i);
phi[i]=i-1;
}
for(int k=0;k<(int)pri.size();k++){
int j=pri[k];
if(i*j>n) break;
prime[i*j]=1;
if(i%j==0){
phi[i*j]=phi[i]*j;
break;
}
phi[i*j]=phi[i]*phi[j];
}
}
}
int main(){
cin>>n;
varphi();
for(int i=1;i<n;i++){
ans+=phi[i];
}
if(n==1) cout<<0;
else cout<<ans*2+1;
return 0;
}
反质数
咕着