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\) ,则有

\[a^n \bmod m=\begin{cases} a^n \bmod m &\text{if}(n<\varphi(m))\\ a^{n\bmod \varphi(m)+\varphi(m)} \bmod m &\text{if}(n\ge \varphi(m)) \end{cases} \]

【模板】扩展欧拉定理

模板题,套上述公式即可。什么你想知道超级大数 \(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\) 此时可得出

\[x=\begin{cases} k = 0 \pmod a\\ k+1 = 0 \pmod b \end{cases} \]

\(k=a\times x\),代入上式化简得到:

\[a\times x = -1 \pmod b \]

枚举 \(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}\) 的形式。
那么原题就变为求:

\[\frac{10^n-1}{9}=k\pmod m \]

略微化简,得到如下式子:

\[10^n = 9\times k +1 \pmod m \]

求最小整数 \(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;
}

反质数

咕着

posted @ 2025-03-01 17:33  Tighnari  阅读(41)  评论(0)    收藏  举报