目录
一、质数
1.试除法判断质数
判定一个数n是否是质数,主要判定2 ~ sqrt(n)之间是否有其约数,并且,所有小于2的数不是质数。
注意在枚举2-sqrt(n)之间所有数时,不推荐i<=sqrt(n)和i*i<=n这两种写法,原因是每次循环都计算sqrt(n)速度比较慢,而i*i<=n当n去INT_MAX附近时容易造成溢出,死循环。推荐的写法是i<=n/i;
#include<iostream>
#include<cmath>
using namespace std;
bool isPrime(int x){
if(x<2) return false;
for(int i=2;i<=x/i;i++){
if(x%i==0) return false;
}
return true;
}
int main(){
int n;cin>>n;
while(n--){
int x;cin>>x;
if(isPrime(x)) puts("Yes");
else puts("No");
}
return 0;
}
2.试除法分解质因数
#include<iostream>
using namespace std;
void divide(int n){
for(int i=2;i<=n/i;i++){
if(n%i==0){//i一定是质数,因为一旦是因数,会把这个因数直到除以干净
int s=0;
while(n%i==0){
n/=i;
s++;
}
printf("%d %d\n",i,s);
}
}
if(n>1) printf("%d %d\n",n,1);//处理大于sqrt(n)的质因数
puts("");
}
int main(){
int n;scanf("%d",&n);
while(n--){
int x;scanf("%d",&x);
divide(x);
}
return 0;
}
3.筛选法找质数
(1)朴素筛法--埃氏筛
筛质数的目的在于求出1 ~ n之间的质数。那么比较快速的办法就是将1 ~ n当中不是质数的数给筛出去。
朴素筛法是利用已经确定了的质数进行筛除,其原理是将一个质数的所有小于等于n的倍数全部筛除。
#include<iostream>
using namespace std;
const int N=1000010;
int primes[N],cnt;
bool st[N];
void get_primes(int n){
for(int i=2;i<=n;i++){
if(st[i]) continue;//该数被筛掉,跳过该循环
primes[cnt++]=i;//加入素数表
for(int j=i+i;j<=n;j+=i) st[j]=true;//删去该素数的倍数
}
}
int main(){
int n;cin>>n;
get_primes(n);
cout<<cnt<<endl;
return 0;
}
(2)线性筛法
当数据在10^6时,线性筛法和埃氏筛法速度差不多,10^7时线性筛法比埃氏筛法快一倍。
#include<iostream>
using namespace std;
const int N=1000010;
int primes[N],cnt;
bool st[N];
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++){//没必要写j<cnt
st[primes[j]*i]=true;
if(i%primes[j]==0) break;
}
}
}
int main(){
int n;cin>>n;
get_primes(n);
cout<<cnt<<endl;
return 0;
}
二、约数
1.试除法求约数
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
vector<int> get_divisors(int n){
vector<int> res;
for(int i=1;i<=n/i;i++){
if(n%i==0){
res.push_back(i);
if(i!=n/i) res.push_back(n/i);
}
}
sort(res.begin(),res.end());
return res;
}
int main(){
int n;cin>>n;
while(n--){
int x;cin>>x;
auto res=get_divisors(x);
for(auto t:res) cout<<t<<" ";
puts("");
}
return 0;
}
2.约数个数问题
int范围内的数的约数个数最多是1500个左右。
该算法求解的是一个数x
的所有2 ~ x - 1
中的约数个数
该算法基于约数个数公式(a₁ + 1) * (a₂ + 1) * … * (ak + 1)
,其中a1
为x
的第一个质因子的指数,a₂
为x
的第二个质因子的指数,依次类推。
#include<iostream>
#include<unordered_map>
using namespace std;
const int N=1e9+7;
int main(){
int n;cin>>n;
unordered_map<int,int> primes;
while(n--){
int x;cin>>x;
for(int i=2;i<=x/i;i++){
while(x%i==0){
x/=i;
primes[i]++;
}
}
if(x>1) primes[x]++;
}
long long res=1;
for(auto prime:primes) res=res*(prime.second+1)%N;
cout<<res<<endl;
return 0;
}
3.约数之和问题
该算法同样基于公式,(p1^0 + p1^1 + … + p1^a1) * … * (pk^0 + pk^1 + … + pk^ak)。其中,p1是第一个质因子,a1是第一个质因子的指数。
上式中,可以利用t = t * p + 1求解每一项,例如:t1 = 1,t1 = p + 1,t2 = p^2 + p + 1,…,ta = p^a1 + …… + 1
#include<iostream>
#include<vector>
#include<unordered_map>
using namespace std;
const int N=1e9+7;
int main(){
int n;cin>>n;
unordered_map<int,int> primes;
while(n--){
int x;cin>>x;
for(int i=2;i<=x/i;i++){
while(x%i==0){
x/=i;
primes[i]++;
}
}
if(x>1) primes[x]++;
}
long long res=1;
for(auto prime:primes){
int p=prime.first,a=prime.second;
long long t=1;
while(a--) t=(t*p+1)%N;
res=res*t%N;
}
cout<<res<<endl;
return 0;
}
4.欧几里得算法(辗转相除法)
如果一个数d能整除a,且能整除b,那么d一定能整除c1 * a + c2 * b。所以d也能够整除a - c * b,令c = (a / b)向下取整,则a - c * b = a mod b,所以d也能整除a mod b,故a, b两个数的最大公约数等于b, a mod b这两个数的最大公约数。这就是欧几里得算法的核心之处。
#include<iostream>
using namespace std;
int gcd(int a,int b){
// int c;
// while(b){
// c=a%b;
// a=b;
// b=c;
// }
// return a;
return b?gcd(b,a%b):a;
}
int main(){
int n;cin>>n;
while(n--){
int a,b;cin>>a>>b;
cout<<gcd(a,b)<<endl;
}
return 0;
}
三、欧拉函数
1.公式法求欧拉函数
首先看互质的定义:
#include<iostream>
using namespace std;
int main(){
int n;scanf("%d",&n);
while(n--){
int x;scanf("%d",&x);
int res=x;
for(int i=2;i<=x/i;i++){
if(x%i==0){
res=res/i*(i-1);//整数运算
while(x%i==0) x/=i;
}
}
if(x>1) res=res/x*(x-1);
printf("%d\n",res);
}
return 0;
}
2.线性筛法求欧拉函数
a.一个质数n的欧拉函数为值为n-1。
b.当i%pj==0时,pj是i的一个质因子,所以pj*i的分解质因数的质因数相同,只是pj那一项的次数多1。所以可推导pj*i的欧拉函数值为i的欧拉函数值*pj。
c.当i%pj!=0时,pj不是i的质因子,但也是pj*i的最小质因子。因此在pj*i的欧拉减函数中,只是比i多了一个质因子pj和系数pj。而pj*(1-1-pj)=(pj-1)//质数的欧拉函数公式。=>(pj-1)*phi(i)
d.别忘了1的欧拉函数值为1(1和自己互质)。
#include<iostream>
using namespace std;
const int N = 1000010;
typedef long long ll;
int primes[N], cnt;
int phi[N];
bool st[N];
ll get_eulers(int n) {
ll ans = 1;
for (int i = 2;i <= n;i++) {
if (!st[i]) {//当i为质数时
primes[cnt++] = i;
phi[i] = i - 1;
ans += phi[i];
}
for (int j = 0;primes[j] <= n / i;j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) {//当pj为i的最小质因子时
phi[primes[j] * i] = primes[j] * phi[i];
ans += phi[primes[j] * i];
break;
}
//当pj不是i因子,但是pj*i的最小质因子时
phi[primes[j] * i] = (primes[j] - 1) * phi[i];
ans += phi[primes[j] * i];
}
}
return ans;
}
int main() {
int n;scanf("%d", &n);
printf("%ld\n", get_eulers(n));
return 0;
}
3.欧拉定理和费马定理
欧拉定理:费马定理:
特别地,当n为质数p时:
四、快速幂
朴素做法O(n)
快速幂O(log n)
快速幂可以快速求解a^b % p的结果。a^b在java中虽然有方法pow可以使用,但是在计算过程中很容易就爆long,而快速幂计算的每一步都mod p,一般就不会爆long。
其思想为先预处理出a^(2^0), a^(2^1),… , a^(2^log(k))的结果,这些数每一个都是前一个的平方。这一步显然是log(b)复杂度的。
再将a^b分解为若干个前面预处理出来的值相乘,即将b分解为前面预处理出来的值的指数相加,这一步可以使用二进制进行计算,例如:十进制中的a^5,5的二进制的101,则5可以写为2^0 + 2^2 ,那么a^5就被分解为a^(2^0) * a^(2^2),此时就可以用预处理出来的值相乘得到。而这一步显然也是log(b)的,因此时间复杂度为log(b)。
#include<iostream>
using namespace std;
typedef long long ll;
//a^k%p
int qmi(int a,int k,int p){
int 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;scanf("%d",&n);
while(n--){
int a,k,p;
scanf("%d%d%d",&a,&k,&p);
printf("%d\n",qmi(a,k,p));
}
return 0;
}
快速幂求逆元
当p是质数时,b模p的乘法逆元为b^(p-2)
根据费马定理:
#include<iostream>
using namespace std;
typedef long long ll;
int qmi(int a,int k,int p){
int 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;scanf("%d",&n);
while(n--){
int a,p;scanf("%d%d",&a,&p);
//有逆元的充要条件是a、p互质
//因为p是质数,所以a、p互质的话<=>a不是p的倍数
if(a%p==0) puts("impossible");
else printf("%d\n",qmi(a,p-2,p));
}
return 0;
}
五、扩展欧几里得算法
想了解扩展欧几里得算法,先引入**裴属定理:**若 a, b 是整数,且 gcd(a , b) = d ,那么对于任意的整数x, y , ax + by都一定是d的倍数,特别地,一定存在整数 x, y,使ax + by = d成立。而扩展欧几里得算法则可以很方便的求解任意正整数a, b的x, y这两个系数。即通过函数exgcd(a, b, x, y)求得系数x, y。值得注意:x, y并不唯一。
由欧几里得算法知,gcd(a, b) = gcd(b, a % b),而a % b = a - a / b * b ,那么在递归求gcd(b, a % b, y, x)时有by + (a % b)x = d,化简得ax + (y - a / b * x)b = d,说明在递归时系数x不用更新(这里的x是指exgcd函数里的x,因为在每次进行递归时,会将实参交换后再复制给形参),只需要更新y。
#include<iostream>
using namespace std;
int exgcd(int a,int b,int &x,int &y){
if(!b){
x=1,y=0;
return a;
}
int d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
int main(){
int n;scanf("%d",&n);
while(n--){
int a,b,x,y;scanf("%d%d",&a,&b);
exgcd(a,b,x,y);
printf("%d %d\n",x,y);
}
return 0;
}
解不唯一:(拓展)求所有解:
求解线性同余方程
可以通过扩展欧几里得算法求解线性同余方程ax ≡ b (mod m)。从取模的定义出发,可以根据ax ≡ b (mod m)构造出ax = my' + b,令y = -y',整理得ax + my = b,当b为a, m的最小公倍数的倍数时,可以利用扩展欧几里得算法进行求解,而当b不是其倍数时,则无解。
当用扩展欧几里得求出一组x0, y0后,此时的x0, y0满足的是ax0 + my0 = gcd(a, m),此时,我们将等式两边同时乘以b / gcd(a, m),得到ax0(b / gcd(a, m)) + my0(b / gcd(a, m)) = b,令x = x0(b / gcd(a, m)),y = y0(b / gcd(a, m)),则此时的x, y即为原线性同余方程的一组解。
#include<iostream>
using namespace std;
typedef long long ll;
int exgcd(int a,int b,int &x,int &y){
if(!b){
x=1,y=0;
return a;
}
int d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
int main(){
int n;scanf("%d",&n);
while(n--){
int a,b,m;scanf("%d%d%d",&a,&b,&m);
int x,y;
int d=exgcd(a,-m,x,y);
if(b%d) puts("impossible");
else printf("%d\n",(ll)x*(b/d)%m);
}
return 0;
}
六、高斯消元
模拟线性代数求解:
#include<iostream>
#include<cmath>
using namespace std;
const int N=110;
const double eps=1e-6;
int n;
double a[N][N];
int gauss(){
int c,r;
for(c=0,r=0;c<n;c++){
int t=r;//t记录当前列绝对值最大的那一行
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;i++) swap(a[t][i],a[r][i]);// 将绝对值最大的行交换到当前行的上面
for(int i=n;i>=c;i--) a[r][i]/=a[r][c]; // 将当前行的主元变为 1,从后往前处理,避免数据被提前覆盖
// 对当前行下面的行进行消元操作
for(int i=r+1;i<n;i++){
if(fabs(a[i][c])>eps){// 如果当前行的当前列元素不为 0,则进行消元操作
for(int j=n;j>=c;j--)
a[i][j]-=a[r][j]*a[i][c];
}
}
r++;
}
if(r<n){
for(int i=r;i<n;i++){ // 检查是否无解,如果某行的最后一个元素不为 0 但主元为 0,则无解
if(fabs(a[i][n])>eps) return 2;//无解
}
return 1;//无穷多组解
}
//唯一解
for(int i=n-1;i>=0;i--){
for(int j=i+1;j<n;j++){
a[i][n]-=a[i][j]*a[j][n];// 回代求解,消除未知数
}
}
return 0;
}
int main(){
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<n+1;j++)
cin>>a[i][j];
int t=gauss();
if(t==0){
for(int i=0;i<n;i++) printf("%.2lf\n",a[i][n]);
}else if(t==1) puts("Infinite group solutions");
else puts("No solution");
return 0;
}
标签:约数,return,int,res,质数,--,include,高斯消 From: https://blog.csdn.net/a2946501058/article/details/145210031