Acwing 第四章 数学知识
基础课第四章 数学知识:质数、约数、欧拉函数、快速幂、exgcd、高斯消元、求组合数、容斥原理
一、质数
866. 试除法判定质数
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 105;
bool isprime(int x)
{
if(x<2) return false;
for(int i=2;i<=x/i;i++) //用i<=x/i多快好省,用sqrt(x)很慢,用i*i<=x可能爆int
{
if(x%i==0) return false;
}
return true;
}
int main()
{
int n,x;
cin>>n;
while(n--)
{
cin>>x;
if(isprime(x)) puts("Yes");
else puts("No");
}
return 0;
}
867. 分解质因数
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
void depart(int x)
{
for(int i=2;i<=x/i;i++) //n中最多只含有一个大于sqrt(n)的因子
//对其进行优化,先考虑比sqrt(n)小的
{
if(x%i==0) //i一定是质数
{
int s=0;
while(x%i==0)
{
x = x/i;
s++;
}
cout<<i<<" "<<s<<endl;
}
}
if(x > 1) cout<<x<<" "<<1<<endl; //如果n还是>1,说明这就是大于sqrt(n)的唯一质因子
puts("");
}
int main()
{
int n,x;
cin>>n;
while(n--)
{
cin>>x;
depart(x);
}
return 0;
}
线形筛素数法(埃筛法) O(N)
868. 筛质数
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e6+10;
int n;
int prime[N],cnt; //素数数组及其个数
bool st[N]; //是否被筛除
void get_primes(int n) //埃式筛法
{
for(int i=2;i<=n;i++)
{
if(!st[i])
{
prime[cnt++] = i;
for(int j=i*2;j<=n;j+=i) //只将所有质数的倍数筛去
{
st[j] = true;
}
}
}
}
void get_primes(int n) //线性法
{
for(int i=2;i<=n;i++)
{
if(!st[i]) prime[cnt++] = i;
for(int j=0;prime[j] <= n/i;j++)
{
st[prime[j]*i] = true;
if(i % prime[j] == 0) break; //prime[j]一定是i的最小质因子
}
}
}
int main()
{
cin>>n;
get_primes(n);
cout<<cnt<<endl;
return 0;
}
二、约数
869. 试除法求约数
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int M = 105;
int n,x;
vector<int>divisior(int x)
{
vector<int>res;
for(int i=1;i<=x/i;i++)
{
if(x%i==0)
{
res.push_back(i);
if(x/i != i) res.push_back(x/i); //当遇到i为x的根号时,只能输出一次约数
}
}
sort(res.begin(),res.end());//题目要求结果有序
return res;
}
int main()
{
cin>>n;
while(n--)
{
cin>>x;
vector<int> v = divisior(x);
for(auto t:v)
{
cout<<t<<" ";
}
puts("");
}
return 0;
}
870. 约数个数
思路:对于每个因数,指数从0到α-1共α个供选择构成约数,故得到公式
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
const int M = 2e9+10,MOD = 1e9+7;
unordered_map<int,int>primes; //用哈希表来存储所有质因数的指数
int n,x;
typedef long long LL; //答案非常大,用LL存储
void get_cnt(int x)
{
for(int i=2;i<=x/i;i++) //求x的质因数及其指数
{
while(x%i==0)
{
primes[i]++;
x = x/i;
}
}
if(x>1) primes[x]++; //大于sqrt(x)的质因数
}
int main()
{
cin>>n;
while(n--)
{
cin>>x;
get_cnt(x);
}
LL res = 1;
for(auto k:primes)
{
res = (res * (k.second+1)) % MOD ; //注意:每次乘完都需要取余
}
cout<<res<<endl;
return 0;
}
871. 约数之和
#include<iostream>
#include<map>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 2e9+10,MOD = 1e9+7;
unordered_map<int,int>primes;//用哈希表存储质因数及其指数
int n,x;
void get_primes(int x)//求质因数及其指数
{
for(int i=2;i<=x/i;i++)
{
while(x%i==0)
{
primes[i]++;
x = x/i;
}
}
if(x>1) primes[x]++;//处理大于sqrt(x)的质因数
}
int main()
{
cin>>n;
while(n--)
{
cin>>x;
get_primes(x);
}
LL res = 1;
for(auto prime:primes)
{
LL tmp = 1;
int p = prime.first;//质因数
int e = prime.second;//指数
while(e--)//注意循环次数为e,不是e-1
{
tmp = (tmp * p + 1) % MOD; // p^0 + p^1 +……+p^e秦九韶算法,注意要取模
}
res = (res * tmp) % MOD;//注意每次运算对答案也取模
}
cout<<res<<endl;
return 0;
}
872. 最大公约数
#include<iostream>
using namespace std;
typedef long long LL;
int n;
int a,b;
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
int main()
{
cin>>n;
while(n--)
{
cin>>a>>b;
cout<<gcd(a,b)<<endl;
}
return 0;
}
三、欧拉函数
定义
873. 欧拉函数
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n,x;
int main()
{
cin>>n;
while(n--)
{
cin>>x;
int res = x;
for(int i=2;i<=x/i;i++)
{
if(x%i==0)//i是x的一个质因数
{
res = (res / i) * (i-1); //先除法后乘法,防止溢出
while(x%i==0)//除干净
{
x = x/i;
}
}
}
if(x>1) res = (res/x) *(x-1);//先除法后乘法,防止溢出
cout<<res<<endl;
}
return 0;
}
874. 筛法求欧拉函数
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 1e6+10;
int primes[N],cnt;
int phi[N];
bool st[N];
LL get_eulers(int n)
{
phi[1] = 1;
for(int i=2;i<=n;i++)
{
if(!st[i])
{
primes[cnt++] = i;
phi[i] = i-1;
}
for(int j=0;primes[j] <= n/i;j++)
{
st[primes[j]*i] = true;
if(i % primes[j] == 0)
{
phi[primes[j] * i] = phi[i] * primes[j];
break;
}
phi[primes[j] * i] = phi[i] * (primes[j] - 1);
}
}
LL res = 0;
for(int i = 1;i <= n;i++)
{
res += phi[i];
}
return res;
}
int main()
{
int n;
cin>>n;
cout<<get_eulers(n)<<endl;
return 0;
}
四、快速幂
875. 快速幂
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL a,k,p;
LL qmi(LL a,LL k,LL p) //a^k % p log(k)的时间内算出
{
LL res = 1;
while(k)
{
if(k&1) res = (LL)(res*a)%p;
k >>= 1;
a = (LL)(a * a) % p;
}
return res;
}
int main()
{
int n;
cin>>n;
while (n -- )
{
scanf("%lld%lld%lld", &a, &k,&p);
cout<<qmi(a,k,p)<<endl;
}
return 0;
}
876. 快速幂求逆元
a/b mod p 相当于 a*(power(a,p-2) mod p)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL qmi(LL a,LL b,LL p)
{
LL res = 1;
while(b)
{
if(b&1) res = (LL)(res*a)%p;
b >>= 1;
a = (LL)a*a%p;
}
return res;
}
int n;
LL a,p;
int main()
{
scanf("%d",&n);
while(n--)
{
scanf("%lld%lld", &a, &p);
LL t = qmi(a,p-2,p);
if(a%p) cout<<t<<endl;
else puts("impossible");
}
return 0;
}
五、扩展欧几里得
877. 扩展欧几里得算法
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x = 1,y = 0;
return a;
}
int d = exgcd(b,a%b,y,x);
y -= a/b * x;
return d;
}
int n,a,b,x,y;
int main()
{
scanf("%d", &n);
while (n -- )
{
scanf("%d%d", &a, &b);
exgcd(a,b,x,y);
cout<<x<<' '<<y<<endl;
}
return 0;
}
六、高斯消元
883. 高斯消元解线性方程组
//: # (打卡模板,上面预览按钮可以展示预览效果 ^^)
#include<iostream>
#include<cmath>
using namespace std;
const int N = 105;
const double eps = 1e-6;
double a[N][N+1];
int n;
int gauss()
{
int c,r;//c为列,r为行
for(c = 0,r = 0;c < n;c++)
{
int t = r;
for(int i = r;i < n;i++) //找出绝对值最大的一行
{
if(fabs(a[i][c]) > fabs(a[t][c]))
{
t = i;
}
}
if(fabs(a[t][c]) < eps) continue;//若该列全为0,说明已处理完毕,前进到下一列
for(int i = c;i < n+1;i++) swap(a[t][i],a[r][i]);//把最大行与当前一轮处理的第一行交换(不是矩阵第一行)
for(int i = n;i >= c;i--) a[r][i] = a[r][i] / a[r][c];//把最大行乘以某个数,使得第一个数变成1
for(int i = r + 1;i < n;i++)//将下面所有行的第c列消成0,i正序枚举行
{
if(fabs(a[i][c]) > eps)//大于0
{
for(int j = n;j >= c;j--)//j倒序枚举列
{
a[i][j] = a[i][j] - a[r][j] * a[i][c];//a[i][c]第i行第c个数,此处难理解
}
}
}
r++;
}
if(r < n)
{
for(int i = r;i < n;i++)
{
if(fabs(a[i][n]) > eps) return 0;//无解
}
return 2;//无穷多解
}
for(int i = n-1;i >= 0;i--)//将矩阵化简到:系数矩阵有且只有一个1,即可直接求出未知数的值
{
for(int j = i + 1;j <= n;j++)
{
a[i][n] = a[i][n] - a[i][j] * a[j][n];
}
}
return 1;//唯一解
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
for(int j=0;j<n+1;j++)
{
cin>>a[i][j];
}
}
int res = gauss();
if(res == 0) puts("No solution");
else if(res == 1)
{
for(int i = 0;i < n;i++)
{
printf("%.2lf\n",a[i][n]);
}
}
else if(res == 2) puts("Infinite group solutions");
return 0;
}
七、求组合数
C(a,b) = C(a-1,b) + C(a-1,b-1)
#include<iostream>
using namespace std;
const int N = 2005,MOD = 1e9+7;
int c[N][N];//c[a][b] 表示Cba b在上,a在下
void init()//预处理,打表
{
for(int i=0;i<N;i++)
{
for(int j=0;j<=i;j++)
{
if(!j) c[i][j] = 1;//定义规定 i个中选0个的方案数为1
else c[i][j] = (c[i-1][j-1] + c[i-1][j]) % MOD; //必须取余,否则溢出
}
}
}
int main()
{
int n,a,b;
cin>>n;
init();
while(n--)
{
cin>>a>>b;
cout<<c[a][b]<<endl;
}
return 0;
}
快速幂+逆元
//: # (打卡模板,上面预览按钮可以展示预览效果 ^^)
#include<iostream>
using namespace std;
const int N = 1e5+10,MOD = 1e9+7;
typedef long long LL;
int fact[N],infact[N];//次幂数组及其逆元数组
int qmi(int a,int k,int p)
{
int res = 1;
while(k)
{
if(k & 1) res = (LL)res * a % p;
k = k/2;
a = (LL)a * a % p;
}
return res;
}
int main()
{
int n,a,b;
fact[0] = infact[0] = 1;
for(int i = 1;i < N;i++)
{
fact[i] = (LL)fact[i-1] * i % MOD;
infact[i] = (LL)infact[i-1] * qmi(i,MOD-2,MOD) % MOD;//快速幂求逆元
}
cin>>n;
while(n--)
{
cin>>a>>b;
cout<<(LL)fact[a] * infact[b] % MOD * infact[a-b] % MOD<<endl;//cab组合数公式
}
return 0;
}
卢卡斯定理
#include<iostream>
using namespace std;
typedef long long LL;
LL a,b;
int p,n;
int qmi(int a,int b)
{
int res = 1;
while(b)
{
if(b & 1) res = (LL)res * a % p; //遇到乘法后面就要跟取余
b = b / 2;
a = (LL)a * a % p;
}
return res;
}
int C(LL a,LL b) //使用定义和逆元来求组合数即可
{
int res = 1;
for(int i = 1,j = a;i <= b;i++,j--)
{
res = (LL)res * j % p;
res = (LL)res * qmi(i,p-2) % p;//逆元把除法变乘法
}
return res;
}
int Lucas(LL a,LL b)//卢卡斯定理
{
if(a < p && b < p) return C(a,b);
return (LL)C(a % p,b % p) * Lucas(a / p,b / p) % p;
}
int main()
{
cin>>n;
while(n--)
{
cin>>a>>b>>p;
cout<<Lucas(a,b)<<endl;
}
return 0;
}
高精度
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 5005;
int primes[N],sum[N],cnt;
bool st[N];
vector<int> mul(vector<int>a,int b)//高精度乘法
{
vector<int>c;
int t = 0;
for(int i=0;i<a.size();i++)
{
t += a[i] * b;
c.push_back(t % 10);
t = t / 10;
}
while(t)
{
c.push_back(t % 10);
t = t / 10;
}
return c;
}
void get_primes(int n)
{
for(int i=2;i<=n;i++)
{
if(!st[i]) primes[cnt++] = i;
for(int j = 0;primes[j] <= n / i;j++)
{
st[primes[j]*i] = true;
if(i % primes[j] == 0) break;
}
}
}
int get(int n,int p)//对p的各个<=a的次数算整除 下取整倍数
{
int res = 0;
while(n)
{
res += n/p;
n = n/p;
}
return res;
}
int main()
{
int a,b;
cin>>a>>b;
get_primes(a);
for(int i=0;i<cnt;i++)
{
int p = primes[i];
sum[i] = get(a,p) - get(b,p) - get(a-b,p);
}
vector<int>res;
res.push_back(1);
for(int i=0;i<cnt;i++)
{
for(int j = 0;j<sum[i];j++)
{
res = mul(res,primes[i]);
}
}
for(int i = res.size()-1;i>=0;i--)
{
cout<<res[i];
}
puts("");
return 0;
}
八、容斥原理
890. 能被整除的数
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 20;
int p[N],n,m;
int main()
{
cin>>n>>m;
for(int i = 0;i < m;i++)
{
cin>>p[i];
}
int res = 0;
for(int i = 1;i < 1 << m;i++)
{
int mul = 1;//选中的集合对应质数的乘积,用于计算集合中数的数量
int cnt = 0;//选中集合的个数
for(int j = 0;j < m;j++)//把i看作二进制数,枚举i的每一位
{
if(i >> j & 1)//若该位为1,则表明选中该集合
{
if((LL)mul * p[j] > n) // 乘积大于n,则n/mul = 0,不选中此集合,跳出此轮循环
{
mul = -1;//标记乘法异常,不选中
break;
}
cnt++; // 有一个1,集合的个数加1
mul = mul * p[j];
}
}
if(mul == -1) continue;// 乘积大于n,则n/mul = 0,不选中此集合,跳出此轮循环
if(cnt & 1 == 1) res += n / mul;//根据容斥原理,集合个数为奇数时加,偶数时减
else res -= n / mul;
}
cout<<res<<endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)