约数和倍数学习指南
前置芝士
约数
约数,又称因数。整数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]
[乘法逆元+线性筛+质因数分解]
[公式]
定义\(cnt_{i , j}\)表示a_i中第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 满足:
- x 和 a0 的最大公约数是a1;
- x 和b0 的最小公倍数是 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;
}