开发技能算法数论

知识点

  • 模运算
  • 快速幂
  • 素数
  • 质因子
  • GCD/LCM

模运算

定义: 模运算是指计算一个整数除以另一个整数后得到的余数。在数学中,用符号“%”表示模运算,例如 a % b 就表示 a 除以 b 的余数。

模运算是大数运算中的常用操作。 如果一个数太大,无法直接输出,或者不需要直接输出,可以把它取模后,缩小数值再输出。 Python 虽然能直接计算大数,不用担心数据溢出,但是大数乘法太耗时,所以也常用取模来缩小数值。

一个最简单应用,判断奇偶:a%2 == 0,a 是偶数;a%2 == 1,a 是奇数

快速幂

幂运算__ana^n,当 n 很大时,如果一个个地乘,时间是 O(n)的,速度很慢,此时可以用快速幂,在 O(logn)的时间内算出来。

快速幂的一个解法:分治法,算 a2a^2,然后再算(a2)2(a^2)^2,…,一直算到 ana^n,代码也容易写。

标准的快速幂:用位运算实现。

基于位运算的快速幂,原理是倍增。

快速幂的原理:

a11a^{11} 为例说明如何用倍增法做快速幂。

  1. 幂次与二进制的关系。把 a11a^{11} 分解成幂 a8a^8a2a^2a1a^1 的乘积:a11a^{11}= 8a8+2+18a^8+2+1= a8a^8× a2a^2× a1a^1。其中 a1a^1a2a^2a4a^4a8a^8…的幂次都是 2 的倍数,所有的幂 aia^i 都是倍乘关系,逐级递推,代码: a *= a
  2. 幂次用二进制分解。如何把 11 分解为 8+2+1?利用数的二进制的特征,n = 1011 = 232^3+212^1+202^0 = 8+2+1,把 n 按二进制处理就可以。
  3. 如何跳过那些没有的幂次?例如 1011 需要跳过 a4a^4 。做个判断,用二进制的位运算实现: n & 1 取 n 的最后一位,并且判断这一位是否需要跳过。 n >>= 1 把 n 右移一位,目的是把刚处理过的 n 的最后一位去掉。 幂运算的结果往往很大,一般会先取模再输出。 根据取模的性质有:ana^n mod m =(a mod m)n(a\ mod \ m)^n mod m

快速幂模板:

我们使用bP mod  kb^P \ mod \ \ k为例,来写一个快速幂的模板。

代码描述:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;     //变量改用较大的long long
ll fastPow(ll a, ll n, ll mod){
    ll ans = 1;
    a %= mod;          //重要,防止下面的ans*a越界
    while(n) {
        if(n & 1)   ans = (ans*a) % mod;   //取模
        a = a*a % mod;                     //取模
        n >>= 1;
    }
    return ans;
}
int main(){
    ll b,p,k;    cin>>b>>p>>k;
    cout << fastPow(b,p,k);
    return 0;
}
import java.util.Scanner;

public class Main {
    static long fastPow(long a, long n, long mod) {
        long ans = 1;
        a %= mod;   //重要,防止下面的ans*a越界
        while (n > 0) {
            if ((n & 1) == 1) {
                ans = (ans * a) % mod;  //取模
            }
            a = (a * a) % mod;  //取模
            n >>= 1;
        }
        return ans;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        long b = sc.nextLong(), p = sc.nextLong(), k = sc.nextLong();
        System.out.println(fastPow(b, p, k));
        sc.close();
    }
}
def fast_pow(a, n, mod):
    ans = 1
    a %= mod  #重要,防止下面的ans*a越界
    while n > 0:
        if n & 1 == 1:
            ans = (ans * a) % mod  #取模
        a = (a * a) % mod  #取模
        n >>= 1
    return ans

b, p, k = map(int, input().split())
print(fast_pow(b, p, k))

※素数

素数定义:只能被 1 和自己整除的正整数。注:1 不是素数,最小素数是 2。

判断一个数 n 是不是素数:

当 n ≤ 1014 时,用试除法;

当n > 1014 时,试除法不够用,需要用高级算法,例如 Miller_Rabin 算法。

试除法:用[ 2,n1 ][\ 2,n-1\ ]内的所有数去试着除 n,如果都不能整除,就是素数。

优化:把[ 2,n1 ][\ 2,n-1\ ]缩小到 [ 2,n ][\ 2,\sqrt n\ ]。证明:若 n = a×b,其中ana≤\sqrt nbnb≥\sqrt n ,如果 n 有个因子是 a,说明 n 不是素数,b 不用再试。

C++ 语言描述:

int is_prime(int n){
    if(n<=1) return 0;
    for(int i=2;i<=sqrt(n);i++)
        if(n%i==0)  return 0;
    return 1;
}

试除法判断是不是素数的效率还是太低了,我们需要另一种方式来判断是不是素数。

素数筛法是一种常见的寻找素数的算法。具体来说,素数筛法通过对数列进行筛选,逐步剔除素数的倍数来找到素数。

基本思想:从 2 开始,将 2 的倍数标记为合数;然后再从下一个未标记过的数 3 开始,将 3 的倍数标记为合数;依次循环下去,直到筛选完所有小于某个给定数的正整数。

最终,剩下未被标记为合数的数字即为素数。由于这种算法在实现上使用了一个数组进行标记,因此也被称为“埃拉托斯特尼筛法”(Sieve of Eratosthenes)。

素数筛法的优点在于它只需要执行一次线性时间复杂度的操作,就可以快速找到小于给定数的所有素数。因此,在需要频繁进行素数判断的场合,素数筛法是一种非常高效的算法。

我们模拟一下埃氏筛法的实现原理

初始数组{2、3,4,5,6,7,8,9,10,11,12,13,…,n},操作步骤:

  1. 找到下一个未被标记的数 2,筛掉 2 的倍数,得{2,3,4,5,6,7,8,9,10,11,12,13,…}
  2. 找到下一个未被标记的数 3,筛掉 3 的倍数,得{234,5,6,7,8910,11,12,13,…}
  3. 找到下一个未被标记的数 5,筛掉 5 的倍数,得{23456,7,8910,11,12,13,…} 继续以上步骤,直到数组处理完。

那可以写出埃氏筛法的实现代码:

C++ 语言描述:

int primes[N],cnt;
bool bprime[N];
void getPrime(int n){
    memset(bprime,false,sizeof(bprime));
    bprime[0]=true;
    bprime[1]=true;

    for(int i=2;i<=n;i++){
        if(!bprime[i]){
            prime[cnt++]=i;
            for(LL j=i*2;j<=n;j+=i)
                bprime[j]=true;
        }
    }
}

但是埃氏筛法的缺点:例如 6 会被 3 整除,6 会被 2 整除,会被筛两次,所以我们再给出欧氏线性筛法,两种筛法掌握一种即可。

C++ 语言描述:

int primes[N],cnt;
bool bPrime[N];
void getPrimes(int n){
    memset(bPrime,false,sizeof(bPrime));

    for(int i=2;i<=n;i++){
        if(!bPrime[i])
            primes[cnt++]=i;

        for(int j=0;j<cnt&&i*primes[j]<n;j++){
            bPrime[i*primes[j]]=true;
            if(i%primes[j]==0)
                break;
        }
    }
}

质因子

唯一分解定理,又称为质因数分解定理,是指任何一个大于 1 的正整数,都可以表示为一些质数的乘积。并且,这些质数的指数可以不同,但是它们的组合却是唯一的。

换句话说,唯一分解定理告诉我们,一个大于 1 的正整数 n 可以表示为:

其中,p1、p2、…、pn 是不同的质数,k1、k2、…、kn 是正整数。如果一个正整数 n 除了 1 和本身以外不能被其它正整数整除,那么它就是质数,而且其质因子分解就是它本身。

分解质因子也可以用试除法。求 n 的质因子:

  1. 第一步,求最小质因子 p1。逐个检查从 2 到 的所有素数,如果它能整除 n,就是最小质因子。然后连续用 p1 除 n,目的是去掉 n 中的 p1,得到 n1。
  2. 第二步,再找 n1 的最小质因子。逐个检查从 p1 到 的所有素数。从 p1 开始试除,是因为 n1 没有比 p1 小的素因子,而且 n1 的因子也是 n 的因子。
  3. 继续以上步骤,直到找到所有质因子。

GCD/LCM

最大公约数GCD (Greatest Common Divisor)

整数 a 和 b 的 GCD 是指能同时整除 a 和 b 的最大整数,记为 gcd(a, b)。由于-a 的因子和 a 的因子相同,因此 gcd(a, b) = gcd(|a|, |b|)。编码时只关注正整数的最大公约数。

性质:

  1. gcd(a, b) = gcd(a, a+b) = gcd(a, k·a+b)
  2. gcd(ka, kb) = k·gcd(a, b)
  3. 定义多个整数的最大公约数:gcd(a, b, c) = gcd(gcd(a, b), c)。
  4. 若 gcd(a, b) = d,则 gcd(a/d, b/d) = 1,即 a/d 与 b/d 互素。这个定理很重要。
  5. gcd(a+cb, b) = gcd(a, b)

我们在 C++中可以直接使用内置函数 gcd()gcd() 可以对负数使用,符号与第一个参数有关。

#include<bits/stdc++.h>
using namespace std;
int main(){
    cout<<__gcd(15, 81)<<"\n";    // 输出  3
    cout<<__gcd(0, 44)<<"\n";     // 输出  44
    cout<<__gcd(0, 0)<<"\n";      // 输出  0
    cout<<__gcd(-6, -15)<<"\n";   // 输出  -3
    cout<<__gcd(-17,289)<<"\n";   // 输出  -17
    cout<<__gcd(17,-289)<<"\n";   // 输出  17
    return 0;
}

Python 语言和 Java 语言是没有内置 gcd 函数的,我们应该如何编码? 我们采用递归的方式手写辗转相除算法。gcd(a, b) = gcd(b, a mod b)

C++ 语言描述:

#include<bits/stdc++.h>
using namespace std;
int gcd(int a, int b){ return b? gcd(b, a%b):a; }
int main(){
    cout<<gcd(15, 81)<<"\n";    // 输出  3
    cout<<gcd(0, 44)<<"\n";     // 输出  44
    cout<<gcd(0, 0)<<"\n";      // 输出  0
    cout<<gcd(-6, -15)<<"\n";   // 输出  -3
    cout<<gcd(-17,289)<<"\n";   // 输出  -17
    cout<<gcd(17,-289)<<"\n";   // 输出  17
    return 0;
}

如果你觉得三目运算符不太容易理解的话,笔者在这里给大家写一个展开的版本:

C++ 语言描述:

int GCD(int a,int b)
{
    if(b==0)
        return a;
    return GCD(b,a%b);
}

最小公倍数 LCM(the Least Common Multiple)

a 和 b 的最小公倍数 lcm(a, b),从算术基本定理推理得到lcm(a,b)=abgcd(a,b)lcm(a,b)= \frac{a∗b}{gcd(a,b)}。 唯一需要注意的是尽量不要写 lcm=ab/gcd(a,b)lcm=a⋅b/gcd(a,b) ,而要改为 lcm=a/gcd(a,b)blcm=a/gcd(a,b)⋅b ,否则可能会导致乘法溢出,那我们就可以写出 lcm 的函数。

int lcm(int a, int b){    //需要的时候把int改成long long
   return a / gcd(a, b) * b;  //先做除法再做乘法,防止先做乘法溢出
}

Practice

刷题统计

2022 年第十三届省赛,lanqiaoOJ 题号 2098

【问题描述】 小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做 a 道题目,周六和周日每天做 b 道题目。请你帮小明计算,按照计划他将在第几天实现做题数大于等于 n 题?

【输入格式】 输入一行包含三个整数 a, b 和 n。

【输出格式】 输出一个整数代表天数。

【评测用例规模与约定】 对于 50%的评测用例,1 ≤ a, b, n ≤ 10^6;对于 100%的评测用例,1 ≤ a, b, n ≤ 10^18。

解题思路: 求余数的简单题,利用求余,把计算复杂度降为 O(1)。

这道题比较简单我们可以很容易的写出程序。

C++ 语言描述:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
    ll a,b,n; cin>>a>>b>>n;
    ll week = a*5+b*2;     //每周做题
    ll days = (n/week)*7;  //周数
    n %= week;             //剩下的做题数
    if(n<=a*5) days += n/a+(n%a?1:0);  //在周一到周五内
    else{                  //周六和周日
        days += 5, n -= a*5;
        days += n/b+(n%b?1:0);
    }
    cout<<days;
    return 0;
}

快速幂(模板题)

lanqiaoOJ 题号 1514

【题目描述】给定 b, p, k,求(b^p) mod k。其中 2≤b, p, k≤10^9。

【输入描述】三个整数 b,p,k。

【输出描述】输出(b^p) mod k。

解题思路:

这个题目是快速幂的模板题,我可以按照题意套用快速幂的模板即可。

C++ 语言描述:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;     //变量改用较大的long long
ll fastPow(ll a, ll n, ll mod){
    ll ans = 1;
    a %= mod;          //重要,防止下面的ans*a越界
    while(n) {
        if(n & 1)   ans = (ans*a) % mod;   //取模
        a = a*a % mod;                     //取模
        n >>= 1;
    }
    return ans;
}
int main(){
    ll b,p,k;    cin>>b>>p>>k;
    cout << fastPow(b,p,k);
    return 0;
}

RSA 解密

2019 年第十届省赛,填空题,lanqiaoOJ 题号 603

【题目描述】

RSA 是一种经典的加密算法。它的基本加密过程如下。 首先生成两个质数 p、q,令 n=p⋅q,设 d 与(p−1)⋅(q−1) 互质,则可找到 e 使得 d⋅e 除(p−1)⋅(q−1) 的余数为 1。 n、d、e 组成了私钥,n、d 组成了公钥。 当使用公钥加密一个整数 X 时(小于 n),计算 C = X^d mod n,则 C 是加密后的密文。 当收到密文 C 时,可使用私钥解开,计算公式为 X = C^e mod n。 例如,当 p=5,q=11,d=3 时,n=55,e=27。 若加密数字 24,得 24^3 mod 55=19。解密数字 19,得 19^27 mod 55=24。 现在你知道公钥中 n = 1001733993063167141, d = 212353,同时你截获了别人发送的密文 C = 20190324,请问,原文是多少?

解题思路:

  1. 求 p、q (两个质数 p、q, n=p⋅q ) 先求 n 的素因子 p 和 q。由于 n 只有这 2 个因子,没有别的因子,所以 p 和 q 必然有一个小于n\sqrt n,找到一个,另一个就知道了。用暴力法求 p、q,用 i 循环从 2 到 undefinedundefined 一个个试。若 n 除以 i 的余数是 0,i 就是因子。

循环次数是 1001733993063167141=1000866621\sqrt {1001733993063167141} =1000866621,即十亿次计算。

得到:p = 891234941、q = 1123984201。 这个步骤的代码如下:

C++ 语言描述:

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
int main(){
    ll n = 1001733993063167141;
    ll k = sqrt(n);
    for(ll i=2;i<=k;i++)
    if(n % i == 0)     cout<<i<<" "<<n/i;
    return 0;
}
  1. 求 e(找到 e 使得 d⋅e 除(p−1)⋅(q−1) 的余数为 1 ) 用到大数了。c++的 64 位 long long 不够用,但有_int128 类型下面代码打印出 e = 823816093931522017。

注意 e 有很多个,取最小的一个就行了。

C++ 语言描述:

#include<bits/stdc++.h>
using namespace std;
typedef __int128 ll;

void print(__int128 num)
{//递归调用,实现从高位向低位输出
    if(num>9)
        print(num/10);
    putchar(num%10+'0');
}

int main(){
    ll n = 1001733993063167141;
    ll d = 212353;
    ll p = 891234941;
    ll q = 1123984201;
    ll tmp = (p - 1) * (q - 1);
    print(tmp);
    puts("");
    for(ll i=2; i<=n; i++){
        ll now = i * tmp + 1;
        if(now % d == 0){
            ll t=now/d;
            print(t);
            break;  //有很多e,求第一个就行了
        }
    }
    return 0;
}
  1. 求 X = C^e mod n ,这一步骤考了快速幂。 C++ 语言描述:
#include<bits/stdc++.h>
using namespace std;
typedef __int128 ll;
void print(__int128 num)
{//递归调用,实现从高位向低位输出
    if(num>9)
        print(num/10);
    putchar(num%10+'0');
}

ll fastPow(ll a, ll b, ll mod){
    ll ans = 1;
    while(b){
        if(b & 1)  ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}

int main(){
    ll n = 1001733993063167141;
    ll e = 823816093931522017;
    ll C = 20190324;
    print( fastPow(C, e, n));  //打印结果:579706994112328949
    return 0;
}

核桃的数量

2013 年第四届省赛 lanqiaoOJ 题号 210

【题目描述】小张是软件项目经理,他带领 3 个开发组。工期紧,今天都在加班呢。为鼓舞士气,小张打算给每个组发一袋核桃(据传言能补脑)。他的要求是: 1.各组的核桃数量必须相同 2.各组内必须能平分核桃(当然是不能打碎的) 3.尽量提供满足 1, 2 条件的最小数量(节约闹革命嘛)。

【输入格式】输入三个正整数 a, b, c,表示每个组正在加班的人数,用空格分开(a,b,c< 30) 。

【输出格式】输出一个正整数,表示每袋核桃的数量。

解题思路: 简单题,答案就是三个数字的最小公倍数。

C++ 语言描述:

#include<bits/stdc++.h>
using namespace std;
int lcm(int a, int b){ return a / __gcd(a, b) * b;}
int main(){
    int a,b,c;    cin>>a>>b>>c;
    int k = lcm(a,b);
    cout<<lcm(k,c)<<endl;
    return 0;
}

Hankson 的趣味题

lanqiaoOJ 题号 520

【题目描述】在课堂上,老师讲解了如何求两个正整数 c1 和 c2 的最大公约数和最小公倍数。Hankson 认为自己已经熟练地掌握了这些知识,他开始思考一个“求公约数”和“求公倍数”之类问题的“逆问题”,这个问题是这样的:已知正整数 a0,a1,b0,b1,设某未知正整数 x 满足:

  1. x 和 a0 的最大公约数是 a1
  2. x 和 b0 的最小公倍数是 b1 Hankson 的“逆问题”就是求出满足条件的正整数 x。但稍加思索之后,他发现这样的 x 并不唯一,甚至可能不存在。因此他转而开始考虑如何求解满足条件的 x 的个数。请你帮助他编程求解这个问题。

【输入格式】输入第一行为一个正整数 n,表示有 n 组输入数据。 接下来的 n 行每行一组输入数据,为四个正整数 a0,a1,b0,b1,每两个整数之间用一个空格隔开。输入数据保证 a0 能被 a1 整除,b1 能被 b0 整除。 数据规模:对于 100%的数据,保证有 1≤a0,a1,b0,b1≤2,000,000,000 且 n≤2000。 【输出格式】输出共 n 行。每组输入数据的输出结果占一行,为一个整数。对于每组数据: 若不存在这样的 x,请输出 0;若存在这样的 x,请输出满足条件的 x 的个数。

解题思路:

若 x 是 b1 的因子,有 x*y = b1,y 也可能是答案。 只需要在范围 x≤sqrt(b1)内查询,同时判断 y 就行了。 但还是超时,因为 gcd 计算也要花时间,加上一个优化:if(b1%x==0),表示 b1 是 x 的公倍数,即可通过。

C++ 语言描述:

#include<bits/stdc++.h>
using namespace std;
int lcm(int a, int b){ return a / __gcd(a, b) * b;}
int main() {
    int n; scanf("%d",&n);
    while(n--) {
        int a0,a1,b0,b1;
        cin >>a0>>a1>>b0>>b1;
        int ans=0;
        for(int x=1;x <= sqrt(b1);x++){
            if(b1%x == 0){        //优化
                if(__gcd(x,a0)==a1 && lcm(x,b0)==b1)  ans++;
                int y = b1/x;       //另外一个因子
                if(x==y) continue;
                if(__gcd(y,a0)==a1 && lcm(y,b0)==b1)  ans++;
            }
        }
        cout << ans <<endl;
    }
    return 0;
}

寻找整数

2022 年第十三届省赛,填空题,lanqiaoOJ 题号 2131

【题目描述】有一个不超过 1017 的正整数 n,知道这个数除以 2 至 49 后的余数如下表所示,求这个正整数最小是多少。

解题思路:

从表格的第一个数 2 开始,逐个增加后面的数,找满足条件的 n。

  1. 满足第一个条件,除以 2 余 1 的数有:3、5、7、9、…此时步长 k = 2。
  2. 继续满足第二个条件,除以 3 余 2 的数,只能从上一步骤的 3、5、7、9、…中找,有 5、11、17、… 此时步长 k = 6,为什么 k = 6? 实际上是 LCM:k = lcm(2, 3) = 6
  1. 继续满足第三个条件,除以 4 余 1 的数,只能从 5、11、17、…中找,有 5、17、29、…此时步长 k = lcm(2, 3, 4) = 12。
  2. 继续满足第四个条件,….

逐个检查表格,直到满足表格中所有的条件。 计算量极小,只需要对表格中的 2 ~ 49 做 48 次 LCM 即可。

C++ 语言描述:

#include<bits/stdc++.h>
using namespace std;
const int N = 50;
int mod[N] = {0,0,1,2,1,4,5,4,1,2,9,0,5,10,11,14,9,0,11,18,9,11,11,15,17,9,23,20,25,16,29,27,25,11,17,4,29,22,37,23,9,1,11,11,33,29,15,5,41,46};
int main() {
    long long ans = 2 + mod[2];
    long long k = 2;              //从第一个数的步长开始
    for (int i = 3; i < N; i++) {
        while (true) {
            if (ans % i == mod[i]) {   //ans是满足前i个数的解
                k = k / __gcd(k, (long long)i) * i;  //连续做LCM
                break;
            } else {
                ans += k;               //累加新的步长
            }
        }
    }
    cout << ans << endl;
    return 0;
}

最大最小公倍数

lanqiaoOJ 题号 1510

【题目描述】已知一个正整数 n,问从 1~N 中任选出三个数,他们的最小公倍数最大可以为多少。

【输入描述】一个正整数 N。

【输出描述】一个整数表示答案。

解题思路: 贪心,从大的数开始选。不过,简单地把 N 里面最大的三个数相乘,N*(N-1)*(N-2),并不正确,需要分析多种情况。

最小的公倍数是三个数的质因数相乘,如果有几个质因数相同,则比较两数中哪个数的质因数的个数较多。例如 6、7、8 的最小公倍数,先分解因子:6=2×3,7=7×1,8=2×2×2,它们的最小公倍数是 3×7×2×2×2。

大于 1 的两个相邻整数互质,它们没有公共的质因数。如果题目是任选二个数,最大的最小公倍数是 N*(N-1)。

对于连续的三个整数,分两种情况:

  1. N 是奇数。N、N-1、N-2 是奇偶奇,结论是这三个数两两互质,三个数的乘积就是最大的最小公倍数。三个数两两互质,也就是说任意一个质数,只在 N、N-1、N-2 中出现一次。逐个分析质数: 质因数 2,只在 N-1 中出现。 质因数 3,如果在 N 中出现(设 N = 3a),就不会在 N-1 中出现(这要求 N-1 = 3b,无解),也不会在 N-2 中出现(这要求 N-2 = 3b,无解)。 推广到任何一个质数 k,都只会在 N、N-1、N-2 中出现一次,所以三个数互质。
  2. N 是偶数。 如果 N 为偶数,那么 N 与 N-2 最大公约数为 2,所以我们要找下一个质数,此时需要考虑 N 与 N-3 的关系: 如果 N 能被 3 整除,则 N-3 也能被 3 整除,此时 N 与 N-3 不互质,但是 N-1 与 N-3 必然互质(N-1、N-3 都为奇数),所以 N-1、 N-2、N-3 如果 N 不能被 3 整除,则 N-3 也不能被 3 整除,此时 N 与 N-3 互质,所以选择 N、N-1、N-3 。

C++ 语言描述:

#include<bits/stdc++.h>
using namespace std;
int main() {
long long n, ans;
cin >> n;
    if(n <= 2)    ans = n;
    else if(n % 2)   ans = n * (n - 1) * (n - 2);  //n是奇数
    else {                                         //n是偶数
       if(n%3) ans = n * (n-1) * (n-3);          //n没有因数3
       else    ans = (n-1) * (n-2) * (n-3);      //n有因数3
    }
    cout << ans;
    return 0;
}

质数

【题目描述】给定一个正整数 N,请你输出 N 以内(不包含 N)的质数以及质数的个数。

【输入描述】一个正整数 N,n<1000。

【输出描述】两行,第 1 行包含若干个素数,从小到大输出,用空格分开。第 2 行一个整数,表示素数个数。

解题思路: 直接套用筛法的模板即可,其中: bprime[i]记录数 i 的状态,若 bprime [i]=1,表示它被筛掉,不是素数。 用 primes[]存放素数,prime[0]是第一个素数 2。 Cnt 是素数个数计数。

C++ 语言描述:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6;
int primes[N],cnt;
bool bprime[N]; //true表示被筛掉,不是素数
void getPrimes(int n){ //埃氏筛,计算[2, n]内的素数
        memset(bprime,false,sizeof(bprime));
        bprime[0]=true;
        bprime[1]=true;

        for(int i=2;i<=n;i++){
            if(!bprime[i]){
                primes[cnt++]=i;
                for(int j=i*2;j<=n;j+=i)
                    bprime[j]=true;
            }
        }
    }

int main(){
    int n;
    cin >>n;
    getPrimes(n-1);
    for(int i=0;i<cnt;i++)  cout << primes[i]<<" ";
    cout << endl;
    cout << cnt;
}

分解质因数

lanqiaoOJ 题号 1559

【题目描述】求出区间[a,b]中所有整数的质因数分解。

【输入描述】输入一行,包含 2 个整数 a,b。2≤a≤b≤10000。

【输出描述】每行输出一个数的分解,形如 k=a1×a2×a3×…,k 从小到大,a 从小到大。

解题思路: 这是一道求质因子的模板题目,我们按照我们求质因子那部分知识即可写出代码.

C++ 语言描述:

#include<bits/stdc++.h>
using namespace std;
int p[20];  //p[]记录因子,p[1]是最小因子。一个int数的质因子最多有10几个
int c[40];  //c[i]记录第i个因子的个数。一个因子的个数最多有30几个
int factor(int n){
    int m = 0;
    for(int i = 2; i <= sqrt(n); i++)
        if(n%i == 0){
            p[++m] = i, c[m] = 0;
            while(n%i == 0)            //把n中重复的因子去掉
                n/=i, c[m]++;
        }
    if(n>1)                           //没有被除尽,是素数
        p[++m] = n, c[m] = 1;
    return m;                         //共m个因子
}
int main(){
    int a,b;   cin>>a>>b;
    for(int i=a;i<=b;i++){
        int m = factor(i);
        cout<<i<<"=";
        for(int j=1;j<=m;j++){ //第j个因子
            for(int k=1;k<=c[j];k++){    //第j个因子的个数
                cout <<p[j];
                if(k<c[j]) cout <<"*";
            }
            if(j<m) cout <<"*";
        }
        cout<<endl;
    }
    return 0;
}

质因数个数

【问题描述】

给定正整数 nn, 请问有多少个质数是 nn 的约数。

【输入格式】

输入的第一行包含一个整数 nn 。

【输出格式】

输出一个整数, 表示 nn 的质数约数个数。

【样例输入】

396

【样例输出】

3

解题思路:

直接对数字 n 进行质因数分解即可。 i 从 2 开始枚举判断是否为 n 的因子:如果 i 是因子,则把 n 不断除以 i 直到无法整除为止。最终如果 _n _!=1 ,说明在最后的 n 为质数,此时答案加 1。

C++ 语言描述:

#include <iostream>
#include <cmath>
using namespace std;

int main() {
    int n;
    cin >> n;
    int ans = 0;
    int i = 2;
    while (i * i <= n) {
        if (n % i == 0) {
            ans++;
            while (n % i == 0) {
                n /= i;
            }
        }
        i++;
    }
    if (n != 1) {
        ans++;
    }
    cout << ans << endl;
    return 0;
}

也可以使用大数素数分解——Pollard_rho 算法:这是经典的大数素数分解算法模板,此处不展开赘述,可参考:

数数

【问题描述】

任何一个大于 1 的正整数都能被分解为若干个质数相乘, 比如 28=2×2×728=2×2×7 被分解为了三个质数相乘。请问在区间 [2333333, 23333333] 中有多少个正整数 可以被分解为 12 个质数相乘?

**答案提交:**这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一 个整数, 在提交答案时只填写这个整数, 填写多余的内容将无法得

解题思路: 我们需要判断的数字数量大概有 2e72e7 的级别,如果按照朴素的分别质因数的方法,判断每个数最坏的时间复杂度基本是 O(n)O(n) 级别,虽然是填空题,但是这个复杂度想跑出答案也是挺困难的。所以我们需要考虑优化,这里我们需要运用到一个性质:对于一个正整数 nn,它最多只包含一个大于 n\sqrt n 的质因子。

这也非常好证明,如果存在两个及以上,那么相乘的值肯定大于nn了,就不可能都是 nn 的质因子。这样我们判断每个数的复杂度就能降到 O(n)O(\sqrt n) ,这个复杂度是我们可接受的。最终算出答案为:25606

C++ 语言描述:

public class Main {
    public static void main(String[] args) {
        int res=0;
        for (int i = 2333333; i <=23333333; i++) {
            int s=i;
            int c=0;
            for (int j = 2; j <=s/j; j++) {
                while (s%j==0){
                    c++;
                    s/=j;
                }
            }
            if (s>1) c++;
            if (c==12) res++;
        }
        System.out.println(res);
    }
}

最小质因子之和(Easy Version)

题目描述

定义 F(i) 表示整数 i 的最小质因子。现给定一个正整数 N,请你求出 2nF(i)\sum_{2}^{n}F(i)。 输入描述

第 1 行为一个整数 T,表示测试数据数量。

接下来的 TT 行每行包含一个正整数 N。

1≤T≤10^6 1≤T≤10^6, 输出描述

输出共 T 行,每行包含一个整数,表示答案。

解题思路:

模板题

C++ 语言描述:

#include <stdio.h>
#define N 3000010
int primes[N],cnt=0;
long long f[N];
int np[N];
void get_primes(int n) {
    for(int i=2; i<=n; i++) {
        if(!np[i]) {
            primes[cnt++]=i;
            f[i]=i;
        }
        for(int j=0; primes[j]<=n/i; j++) {
            np[primes[j]*i]=1;
            f[primes[j]*i]=primes[j];
            if(i%primes[j]==0) {
                break;
            }
        }
    }
}
int main() {
    get_primes(N-1);
    for(int i=2; i<N; i++) {
        f[i]+=f[i-1];
    }
    int t;
    scanf("%d",&t);
    while(t--) {
        int n;
        scanf("%d",&n);
    printf("%lld\n",f[n]);
    }
    return 0;
}

最大体积

【题目描述】

每个物品有一定的体积(废话),不同的物品组合,装入背包会战用一定的总体积。

假如每个物品有无限件可用,那么有些体积是永远也装不出来的。为了尽量装满背包,附中的 OIER 想要研究一下物品不能装出的最大体积。

题目保证有解,如果是有限解,保证不超过 2×10^9,如果是无限解,则输出 0。 输入描述

第 11 行一个整数 n(n≤10),表示物品的件数。 第 2 行到 N+1 行: 每件物品的体积 (1∼500)。 输出描述

一个整数 ans,表示不能用这些物品得到的最大体积。

解题思路:

利用贝祖定理判断 n 个数是否最大公因数为 1,如果为 1 就用 dp 来获取不能装到的最大体积, 反之就代表有无限大的最大体积,输出 0。关于 dp,外循环是不同物品的体积,内循环是可能达到的总体积。 这里体积上限用 100000,因为体积太大了就算不出来。dp[j]=1 代表体积为 j 可以达到, dp[j]=0 则代表体积为 j 无法达到,转移方程为 if(dp[j-num[i]]=1) dp[j]=1 转移方程的意思是如果不放 num[i]都可以取到体积,那么加上 num[i]就一定能取到 初始状态 dp[0]=1 不放任何物品,体积就是 0

C++ 语言描述:

#include<iostream>
using namespace std;
#define MAX_NUM 100000

int n;
int num[505];            //保存物品的体积
int dp[MAX_NUM];    //保存物品能组成的所有体积

int gcd(int a,int b)    //求两个数的最大公约数
{
    if (a % b == 0)
        return b;
    else
        return gcd(b, a % b);
}

int Bezout()            //裴蜀定理求所有数的最大公约数
{
    int temp = num[0];
    for (int i = 1; i < n; i++)
    {
        temp = gcd(temp, num[i]);
    }
    return temp;
}

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> num[i];

    if (Bezout() == 1)    //如果所有数的最大公约数为1,则有解,否则为无限解
    {
        dp[0] = 1;
        for (int i = 0; i < n; i++)
        {
            for (int j = num[i]; j <= MAX_NUM; j++)
            {
                if (dp[j - num[i]] == 1)    //i=0时,j为num[0]的倍数;
                    //接下来,j为 num[i]中物品体积值组合的结果
                    dp[j] = 1;
            }
        }

        for (int i = MAX_NUM; i >= 0; i--)        //逆序遍历所有的体积结果,将第一个不能组合的数输出后结束
        {
            if (!dp[i])
            {
                cout<<i;
                return 0;
            }
        }
    }

    cout<<"0";    //无限解

    return 0;
}

比例简化

【问题描述】

在社交媒体上,经常会看到针对某一个观点同意与否的民意调查以及结果。例如,对某一观点表示支持的有 1498 人,反对的有 902 人,那么赞同与反对的比例可以简单的记为 1498:902。

不过,如果把调查结果就以这种方式呈现出来,大多数人肯定不会满意。因为这个比例的数值太大,难以一眼看出它们的关系。对于上面这个例子,如果把比例记为 5:3,虽然与真实结果有一定的误差,但依然能够较为准确地反映调查结果,同时也显得比较直观。

现给出支持人数 A,反对人数 B,以及一个上限 L,请你将 AB 化简为 A′ 比 B′,要求在 A′和 B′ 均不大于 LA′和 B′互质(两个整数的最大公约数是 1)的前提下,A′/B′≥A/BA′/B′−A/B 的值尽可能小。

【输入描述】

输入共一行,包含三个整数 ABL,每两个整数之间用一个空格隔开,分别表示支持人数、反对人数以及上限。

其中,1≤A≤106,1≤B≤106,1≤L≤100,A/BL

【输出描述】

输出共一行,包含两个整数 A′,B′,中间用一个空格隔开,表示化简后的比例。

解题思路:

通过先判断 A 和 B 的大小来优化循环次数

C++ 语言描述:

#include <iostream>
#include <fstream>
#include <cmath>

using namespace std;

int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}

int main() {
    int a, b, l;
    cin >> a >> b >> l;
    int A = 0, B = 0;
    double minVal = 1e6;

    if (a >= b) {
        for (int i = 1; i <= l; i++) {
            for (int j = 1; j <= i; j++) {
                if (gcd(i, j) == 1 && static_cast<double>(i) / j >= static_cast<double>(a) / b) {
                    double diff = static_cast<double>(i) / j - static_cast<double>(a) / b;
                    if (diff < minVal) {
                        minVal = diff;
                        A = i;
                        B = j;
                    }
                }
            }
        }
    } else {
        for (int i = 1; i <= l; i++) {
            for (int j = i; j <= l; j++) {
                if (gcd(i, j) == 1 && static_cast<double>(i) / j >= static_cast<double>(a) / b) {
                    double diff = static_cast<double>(i) / j - static_cast<double>(a) / b;
                    if (diff < minVal) {
                        minVal = diff;
                        A = i;
                        B = j;
                    }
                }
            }
        }
    }

    cout << A << " " << B << endl;
    return 0;
}

Built with LogoFlowershow