约数和倍数学习指南

前置芝士

约数

约数,又称因数。整数a除以整数b(b≠0) 除得的商正好是整数而没有余数,我们就说a能被b整除,或b能整除a。a称为b的倍数,b称为a的约数。

倍数

一个整数能够被另一个整数整除,这个整数就是另一整数的倍数。

最小公倍数和最大公约数的联系

\(a=p_{1}^{ka_{1}}p_{2}^{ka_{2}}\cdots p_{s}^{ka_{8}},b=p_{1}^{kb_{1}}p_{2}^{kb_{2}}\cdots p_{s}^{kb_{8}}\)

则对于a和b的情况,二者的最大公约数等于\(p_1^{\min(k_{a_1},kb_1)}p_2^{\min(k_{a_2},kb_2)}\cdots p_s^{\min(k_{a_s},kb_s)}\)

最小公倍数等于\(p_1^{\max(k_{a_1},k_{b_1})}p_2^{\max(k_{a_2},k_{b_2})}\cdots p_s^{\max(k_{a_s},k_{b_s})}\)

由于\(k_a+k_b=\max(k_a,k_b)+\min(k_a,k_b)\),所以可以得到结论:\(\gcd(a,b)\times\operatorname{lcm}(a,b)=a\times b\),即lcm(a,b)=a / gcd(a,b) * b。

要求两个数的最小公倍数,可以先求最大公约数。

裴蜀定理(贝祖定理)

[定义]

设a,b是不全为0的整数,则存在整数x,y,使得ax+by=gcd(a,b)。

[证明]

。。。。。。

欧几里得算法

\(\gcd(a,b)=\gcd(b,a\bmod b)\),如果gcd(a,b)=1,则a和b互质。

[证明]

。。。。。。

[时间效率]

在输入为两个长为n的二进制整数时,欧几里得算法的时间复杂度为O(n)。(换句话说,在默认a, b为同阶的情况下,时间复杂度为O(log max(a,b))。)

[证明]

a>=b,对a取模会让a至少折半,这意味着这一过程最多发生O(log a)=O(n)次。

辗转相减法

大整数取模的时间复杂度较高,而加减法时间复杂度较低。针对大整数,我们可以用加减代替乘除求出最大公约数。

如果a>>b,更相减损术的O(n)复杂度将会达到最坏情况。

[Stein算法优化]

\(2\mid a,2\mid b,\gcd(a,b)=2\gcd\left(\frac{a}{2},\frac{b}{2}\right)\)

否则,若2 | a,因为2 | b的情况已经讨论过了,所以 2!=b。因此\(\gcd(a,b)=\gcd\left(\frac{a}{2},b\right)\)

[时间效率]

优化后的时间复杂度为O(log n)

[证明]

若 2| a,或 2| b,每次递归至少会将a,b之一减半。递归最多递归O(log n)。

求n的约数(试除法)

时间复杂度:O(sqrt(n))

[c++]

vector<int> get_divisiors(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;
}

求[1,n]的各个数的约数和

时间复杂度:O(nlog n)

//不含本身
for(int i=1;i<=1000/2;i++)//循环每个数(小优化:因为i*j<=n j>=2 所以i<=n/2)
	for(int j=2;i*j<=1000;j++)//循环倍数(排除本身,从2开始)
		c[i*j]+=i;//目标数加上约数

求n个数的乘积的约数个数

分解乘积的质因数与分解其约数的质因数得到的结果是相同的

const int mod = 1e9 + 7;
unordered_map<int, int> primes;
ll res=0;
int n;//分解n个数的质因子数
void solve(){
    scanf("%d", &n);
    int x;
    while (n -- )
    {
        scanf("%d", &x);
        for (int i = 2; i <= x / i; ++ i )
        {
            while (x % i == 0)
            {
                x /= i;
                primes[i] ++ ;
            }
        }
        if (x > 1) primes[x] ++ ;
    }
    for (auto prime : primes)
        res = res * (1 + prime.second) % mod;
}

求约数之和

typedef long long LL;
const int mod = 1e9 + 7;
unordered_map<int, int> primes;
int n;//分解n个数的质因子
ll res=1;
void solve()
{
    scanf("%d", &n);
    int x;
    while (n -- )
    {
        scanf("%d", &x);
        for (int i = 2; i <= x / i; ++ i)
        {
            while (x % i == 0)
            {
                x /= i;
                primes[i] ++ ;
            }
        }
        if (x > 1) primes[x] ++ ;
    }
    for (auto prime : primes)
    {
        ll t = 1;
        ll p = prime.first, a = prime.second;
        while (a -- ) t = (t * p + 1) % mod;
        res = res * t % mod;
    }
 }

求两数最大公约数

欧几里得算法

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

高精度计算

高精度模板见:xxxxxx。

Big gcd(Big a, Big b) {
  // 记录a和b的公因数2出现次数
  int atimes = 0, btimes = 0;
  while (a % 2 == 0) {
    a >>= 1;
    atimes++;
  }
  while (b % 2 == 0) {
    b >>= 1;
    btimes++;
  }
  for (;;) {
    // a和b公因数中的2已经计算过了,后面不可能出现a,b均为偶数的情况
    while (a % 2 == 0) {
      a >>= 1;
    }
    while (b % 2 == 0) {
      b >>= 1;
    }
    if (a == b) break;
    // 确保 a>=b
    if (a < b) swap(a, b);
    a -= b;
  }
  return a << min(atimes, btimes);
}

高次幂做法

\(0<a,b≤10^{10000}\)


朴素辗转相减法

def gcd(a, b):
    while b != 0:
        if a > b:
            a = a - b
        else:
            b = b - a
    return a

整个数组的最小公倍数

[problem description]

Given are \(N\) positive integers \(A_1,...,A_N\).

Consider positive integers \(B_1, ..., B_N\) that satisfy the following condition.

Condition: For any \(i, j\) such that \(1 \leq i < j \leq N\), \(A_i B_i = A_j B_j\) holds.

Find the minimum possible value of \(B_1 + ... + B_N\) for such \(B_1,...,B_N\).

Since the answer can be enormous, print the sum modulo (\(10^9 +7\)).

  • \(1 \leq N \leq 10^4\)
  • \(1 \leq A_i \leq 10^6\)
  • All values in input are integers.

【input】

\(N\)
\(A_1\) \(...\) \(A_N\)

【output】

Print the minimum possible value of \(B_1 + ... + B_N\) for \(B_1,...,B_N\) that satisfy the condition, modulo (\(10^9 +7\)).

[solved]

[乘法逆元+线性筛+质因数分解]

[公式]

\[\text{res}=\sum_1^\text{n}\frac{\text{lcm(A1,..,An)}}{\text{Ai}} \]

定义\(cnt_{i , j}\)表示a_i中第j个质因数个数。

\[\mathrm{lcm=\prod_{i=1}^{tot}pow(pri[i],max(cnt_{1,i},...,cnt_{n,j}))}. \]

(tot表示欧拉筛之后得到的质数的个数,第 j个质数指欧拉筛的prime[j])

const ll mod = 1e9 + 7;
const int N = 1e6 + 5;
ll vis[N], pri[N], cnt = 0;
ll a[N], v[N];
ll qmi(ll a,ll b){
	ll res=1;
	while(b){
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}
void solve(){
	for(int i = 2; i < N; i++){
		if(vis[i] == 0) pri[++cnt] = i;
		for(int j = 1; j <= cnt && i * pri[j] < N; j++){
			vis[i * pri[j]] = 1;
			if(i % pri[j] == 0) break;
		} 
	}

	int n;
	cin >> n;
	for(int i = 1; i <= n; i++) cin >> a[i];

	for(int i = 1; i <= n; i++){
		ll x = a[i];
		for(int j = 1; pri[j] * pri[j] <= x; j++){
			ll num = 0;
			while(x % pri[j] == 0) num++, x /= pri[j];
			v[pri[j]] = max(num, v[pri[j]]);
		}
		if(x != 0) v[x] = max(v[x], 1ll);
	}

	ll lcm = 1, res = 0;
	for(int i = 1; i <= cnt; i++) lcm = lcm * qmi(pri[i], v[pri[i]]) % mod;
	for(int i = 1; i <= n; i++) res = (res + lcm * qmi(a[i], mod-2) % mod) % mod;

	cout << res << endl;
}

最小公约数为1的最小花费

[problem description]

给出n张卡片,分别有和\(l_i\)\(c_i\)两个属性 。在一条无限长的纸带上,你可以选择\(c_i\)的钱来购买卡片i,从此以后可以向左或向右跳\(l_i\)个单位。问你至少花多少元钱才能够跳到纸带上全部位置。若不行,输出-1。

[solved]

我们可以把纸条想象成坐标轴,起始位置为0,假设当前我们花费\(c_k\),得到卡片k,则可以走的位置为:\(S=\mathrm{0+\sum_{i=1}^kx_i\times l_i\left(k\text{未知,}x_i\text{为一个常数}\right)}\)

这样一来我们要遍历到每个点的话,\(S=\mathrm{\sum_{i=1}^kx_i\times l_i=1}\),根据裴蜀定理,我们需要得到\(gcd(l_1,l_2,......,l_k)=1\)的一组卡片。

我们就要找到最小花费的组合使gcd=1

[动态规划]

[c++]

const int N = 310;
int n;
int l[N], c[N];
unordered_map<int, int> dp;
int gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}
void solve() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> l[i];
    }
    for (int i = 1; i <= n; i++) {
        cin >> c[i];
    }
    //小技巧,这里我们通过判断是否为0来进行操作,为0为第一次出现的位置
    for (int i = 1; i <= n; i++) {
        dp[l[i]] = dp[l[i]] ? min(dp[l[i]], c[i]) : c[i];
    }
    for (int i = 1; i <= n; i++) {
        for (auto d : dp) {
            int x = gcd(l[i],d.first);
            dp[x]=dp[x]?min(dp[x],d.second+c[i]):d.second+c[i];
        }
    }
    if(dp[1]) cout<<dp[1]<<endl;
    else cout<<-1<<endl;
}

倍数和约数结合的相关性质题1

[problem description]

已知正整数a0,a1,b0,b1,设某未知正整数 x 满足:

  1. xa0 的最大公约数是a1;
  2. xb0 的最小公倍数是 b1。

求出满足条件的正整数 x的个数

\(1\leq a_0,a_1,b_0,b_1\leq2\times10^9\)

n=2000

[solved]

由最大公因数和最小公倍数的性质可知,p=a0/a1,q=b1/b0,它们与X的公因数为1,并且x也是b1(最小公倍数)的一个因子,所以 令x从1到sqrt(b1)中是b1(最小公倍数)的一个因子,除此外还有b1/x ,也可能是其中一个因子,用以上条件判断。

int gcd(int a,int b) { //求最大公因数 
    return b==0?a:gcd(b,a%b);
}
void solve(){
	int a0,a1,b0,b1;
        cin>>a0>>a1>>b0>>b1;
        int p=a0/a1,q=b1/b0,ans=0;
        for(int x=1;x*x<=b1;x++) 
            if(b1%x==0){  //x是b1的一个因子 
                if(x%a1==0&&gcd(x/a1,p)==1&&gcd(q,b1/x)==1) ans++;
                int y=b1/x;      //y是另一个因子 
                if(x==y) continue; 
                if(y%a1==0&&gcd(y/a1,p)==1&&gcd(q,b1/y)==1) ans++;
            }
        cout<<ans<<endl;
}
posted @ 2023-10-09 13:04  White_Sheep  阅读(53)  评论(0编辑  收藏  举报