2025.3.1数学听课笔记
质数
一到
可以在
质数筛法:埃氏筛和欧拉筛
埃氏筛:从小到大枚举,将他所有的非平凡倍数标记为“非质数”。
欧拉筛:考虑让每个数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;
}
}
质因数分解
#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;
}
#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)的乘积。
即
约数和:质因子分解后,
即
通过质因数分解求因数:写一个 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;
}
&
在质因子分解下为指数上的取
#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;
}
欧拉函数
互质:两个数的最大公因数为
欧拉函数:
即
当
下面是
//求单个值的欧拉函数
#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;
}
这个是线性求
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];
}
}
}
同余
-在模
同余的性质
对于整数
1.自反性:
2.对称性:若
3.传递性:若
4.同加性:若
5.同乘性:若
6.同幂性:若
!同余没有可除性!
欧拉定理
若正整数
广义欧拉定理
对于所有
【模板】扩展欧拉定理
模板题,套上述公式即可。什么你想知道超级大数
介绍一下现学的大整数取模:
对于一个数
#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;
}
裴蜀定理
形如
【模板】裴蜀定理
#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;
}
逆元
费马小定理,扩展欧几里得(EXgcd)。
下面是扩展欧几里得(exgcd)
发现上式在
解出来的
同余方程
#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)
给定
【模板】中国剩余定理(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)
解法:
已知
有想法了吗?是上述的 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 北上广深算法(确信)
给定一个质数
[TJOI2007] 可爱的质数/【模板】BSGS
朴素做法枚举
#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 算法了:分块(优雅的暴力)。
原式为:
我们可以预处理出所有的 欸嘿我不会 hash ,我用 map。
用 hash 的时间复杂度是
#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
求最小的
跟数竞生一顿商讨后,他们告诉我要构造CRT,那就试试吧。
令
令
枚举
注意开 __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;
}
沙拉公主的疑惑
给定
咕着
多少个1?
若干个
那么原题就变为求:
略微化简,得到如下式子:
求最小整数
中间需要开 __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;
}
仪仗队
可观测的坐标需要
需要线性求出
#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;
}
反质数
咕着
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现