数学知识--数论

目录

质数

素数定理:小于n的正整数大约有 \(\frac{n}{\ln n}\) 个素数

试除法判断素数\(O(\sqrt n)\)

点击查看代码
bool is_prime(int x)
{
    for(int i = 2; i <= x / i; i ++)
        if(x % i == 0)
            return false;
    return true;
}

分解质因数 \(O(\sqrt n)\)

点击查看代码
void getdiv(int x)
{
    for(int i = 2; i <= x / i; i ++)
    {
        if(x % i == 0)
        {
            int s = 0;
            while(x % i == 0) s ++, x /= i;
            cout << i << ' ' << s << '\n';
        }
    }
    if(x > 1) cout << x << ' ' << 1 << '\n';
}

素数筛

1. 诶氏筛法 \(O(n\log \log n)\)

点击查看代码
void init_prime(int n)
{
    for(int i = 2; i <= n; i ++)
        if(!st[i])
        {
            prime[cnt ++] = i;
            for(int j = i * i; j <= n; j += i)
                st[j] = true;
        }
}

线性筛 \(O(n)\)

点击查看代码
void init_prime(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[i * prime[j]] = true;
            if(i % prime[j] == 0) break;
        }
    }
}

约数

唯一分解定理:任何一个大于1的自然数 N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积:

\[N = {p_1}^{a_1} * {p_2}^{a_2} * ··· * {p_k}^{a_k} \]

应用

1. 约数个数

若一个正整数\(S\)的标准分解式 \(N = {p_1}^{a_1} * {p_2}^{a_2} * ··· * {p_k}^{a_k}\),则其正因数个数:
\(S_1(N) = (1 + a_1)(1+a_2)···(1+a_k)\)

点击查看代码
void S1(int x)
{
    unordered_map<int,int> hash;
    for(int i = 2; i <= x / i; i ++)
        while(x % i == 0)
            x /= i, hash[i] ++;
    if(x > 1) hash[x] ++;
     for(auto i:hash) ans = ans * (i.second + 1) % mod;
     cout << ans << '\n';
}

2. 约数之和

\(N\)的正因数之和:\(S_2(N) = (1+p_1+{p_1}^2+···+{p_1}^{a_1})(1+p_2+{p_2}^2+···+{p_2}^{a_2})···(1+p_k+{p_k^2}+···+{p_k}^{a_k})\)

点击查看代码
void S2(int x)
{
    unordered_map<int, int> hash;
    for(int i = 2; i <= x / i; i ++)
        while(x % i == 0)
            x /= i, hash[i] ++;
    if(x > 1) hash[x] ++;
    int ans = 1;
    for(auto i : hash)
    {
        int a = i.first, b = i.second, t = 1;
        while(b --) t = (a * t + 1) % mod;
        ans = ans * t % mod;
    }
    cout << ans << '\n';
}

3. 求解 \(a,b\)\(gcd,lcm\)

\[gcd(a,b) = {p_1}^{min(a_1,\ b_1)}{p_2}^{min(a_2,\ b_2)}···{p_k}^{min(a_k,\ b_k)} \]

\[lcm(a,b) = {p_1}^{max(a_1,\ b_1)}{p_2}^{max(a_2,\ b_2)}···{p_k}^{max(a_k,\ b_k)} \]

4. 求组合数

在不取 \(mod\) 的情况下,用唯一分解求组合数:

\[C(n, m) = \frac{n!}{m!*(n-m)!} \]

试除法求所有约数\(O(\sqrt n)\)

点击查看代码
vector<int> get_x(int x)
{
    vector<int> s;
    for(int i = 2; i <= x / i; i ++)
        if(x % i == 0)
        {
            s.push_back(i);
            if(x / i != i)
                s.push_back(x / i);
        }
    return s;
}

欧拉函数 \(O(logn)\)

欧拉函数:\(1∼N\) 中与 \(N\) 互质的数的个数被称为欧拉函数,记为 \(ϕ(N)\)。若在算数基本定理中,\(N={p_1}^{a_1}{p_2}^{a_2}…{p_m}^{a_m}\),则:
\(ϕ(N) = N×\frac{p_1−1}{p_1}×\frac{p_2−1}{p_2}×…×\frac{p_m−1}{p_m}\)

点击查看代码
int erula(int x)
{
    int ans = x;
    for(int i = 2; i <= x / i; i ++)
        if(x % i == 0)
        {
            ans = ans / i * (i - 1);
            while(x % i == 0) x /= i;
        }
    if(x > 1) ans = ans / x * (x - 1);
    return ans;
}

筛法求欧拉函数

(1) 质数i的欧拉函数即phi[i]=i−1,1 i−1均与i互质,共i−1个。

(2) phi[primes[j]∗i] 分为两种情况:

\(①\)\(prime[j]\ |\ i\) : \(primes[j]\)\(i\) 的最小质因子,也是 \(primes[j]∗i\) 的最小质因子,因此 \(1−1/primes[j]\) 这一项在 \(phi[i]\) 中计算过了,只需将基数 \(N\) 修正为 \(primes[j]\) 倍,最终结果为\(phi[i]∗primes[j]\)
\(②\)$\ :\ $$primes[j]$ 不是 \(i\) 的质因子,只是 \(primes[j]∗i\) 的最小质因子,因此不仅需要将基数N修正为 \(primes[j]\) 倍,还需要乘上 \(\frac{prime[j]−1}{primes[j]}\) 这一项,因此最终结果 \(phi[i]∗(primes[j]−1)\)

点击查看代码
void 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] * i <= n; 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);
        }
    }
}

例 可见的点

题目描述:

在一个平面直角坐标系的第一象限内,如果一个点 \((x,y)\) 与原点 \((0,0)\) 的连线中没有通过其他任何点,则称该点在原点处是可见的。

例如,点 \((4,2)\) 就是不可见的,因为它与原点的连线会通过点 \((2,1)\)

部分可见点与原点的连线如下图所示:

编写一个程序,计算给定整数 \(N\) 的情况下,满足 \(0≤x,y≤N\) 的可见点 \((x,y)\) 的数量(可见点不包括原点)。

输入格式:

第一行包含整数 \(C\),表示共有 \(C\) 组测试数据。

每组测试数据占一行,包含一个整数 \(N\)

输出格式:

每组测试数据的输出占据一行。

应包括:测试数据的编号(从 1 开始),该组测试数据对应的 \(N\) 以及可见点的数量。

同行数据之间用空格隔开。

输入

4
2
4
5
231

输出

1 2 5
2 4 13
3 5 21
4 231 32549

数据范围

\(1≤N,C≤1000\)

点击查看代码
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr),cout.tie(nullptr)
#define int long long

using namespace std;

const int N = 1010;

int prime[N], phi[N];
bool st[N];
int n, cnt, t, ans;

void init()
{
    st[1] = 1;
    for(int i = 2; i < N; i ++)
    {
        if(!st[i])
        {
            prime[cnt ++] = i;
            phi[i] = i - 1;
        }
        for(int j = 0; prime[j] * i <= N; j ++)
        {
            st[i * prime[j]] = true;
            if(i % prime[j] == 0)
            {
                phi[i * prime[j]] = phi[i] * (prime[j]);
                break;
            }
            else phi[i * prime[j]] = phi[i] * (prime[j] - 1);
        }
    }
}

void solve()
{
    cin >> n;
    t ++, ans = 0;

    for(int i = 1; i <= n; i ++)
        ans += phi[i] * 2;
    cout << t << ' ' << n << ' ' << ans + 3 << '\n';
}

int main()
{
    IOS;
    init(); int T = 1;
    cin >> T;
    while(T --)
        solve();
    return T ^ T;
}

快速幂 \(O(log n)\)

数的乘方满足性质:\(a^n*a*m = a^{(n+m)}\)

又因
\(n=2^0+2^1+2^2+···+2^{k_1}\)
\(m=2^0+2^1+2^2+···+2^{k_2}\)

故可预处理出\(a^{2^0},a^{2^1},a^{2^2},···,a^{2^{log\ {n+m}}}\)

\(b=n+m\)

\(a^b\)\(a^{2^0},a^{2^1},a^{2^2},···a^{2^{log\ b}}\),即组合成\(a^b=a^{2^{x_1}}\times a^{2^{x_1}}\times a^{2^{x_2}}\times ···\times a^{2^{x_t}}\)

二进制可以表示所有数,且用单一用二进制表示时,b单一表示最大可表示为二进制形式的2logb

点击查看代码
int qmi(int a, int b, int p)
{
    int ans = 1;
    while(b)
    {
        if(b & 1) ans = ans * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return ans;
}

快速幂求逆元 \(O(log n)\)

**乘法逆元的定义:若整数 $ b,m$ 互质,并且对于任意的整数 \(a\),如果满足 \(b|a\),则存在一个整数 \(x\),使得 \(a/b≡a×x(modm)\),则称 \(x\)\(b\) 的模 \(m\) 乘法逆元,记为 \(b^{−1}(modm)\)
\(b\) 存在乘法逆元的充要条件是 \(b\) 与模数 \(m\) 互质当模数 \(m\) 为质数时,\(b^{m−2}\) 即为 \(b\) 的乘法逆元。

裴属定理(贝祖定理):对于任意不全为零的整数 \(a, b\) ,存在 \(x, y\) , 使得 \(ax+by=gcd(a,b)\)

费马小定理:假如 \(a\) 是一个整数,\(p\) 是一个质数,则 \(p|(a^p-a)\),即 \(a^p\equiv a(mod\ p)\),如果 \(a\) 不是 \(p\) 的倍数,则有 \(a^{p-1}\equiv 1(mod\ p)\)

\(b,m\)互质,有

\[a/b\equiv a\times x(mod\ m) \]

\[bx\equiv 1(mod\ m) \]

又有$$b^{m-1}\equiv 1(mod\ m)$$

\[x=b^{-1}=b^{m-2} \]

\(b\)\(m\) 的乘法逆元即为 \(b^{m - 2}\)

点击查看代码
int qminv(int a, int p)
{
    int ans = 1, b = p - 2;
    while(b)
    {
        if(b & 1) ans = ans * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return ans;
}

矩阵乘法 \(O(n^3log(m))\)

矩阵的乘积和数的乘积具有类似的幂次性:\({F_n}^a*{F_n}^b=F_n^{a+b}\)

\[\left[ \begin{matrix} a_{11} & a_{12} & a_{13}\\ a_{21} & a_{22} & a_{23} \\ a_{31} & a_{32} & a_{33} \end{matrix} \right]^m\times\left[ \begin{matrix} a_{11} & a_{12} & a_{13}\\ a_{21} & a_{22} & a_{23} \\ a_{31} & a_{32} & a_{33} \end{matrix} \right]^n=\left[ \begin{matrix} a_{11} & a_{12} & a_{13}\\ a_{21} & a_{22} & a_{23} \\ a_{31} & a_{32} & a_{33} \end{matrix} \right]^{(n+m)}\]

可利用矩阵的幂次性结合快速幂加速求解一些有关系的数据

令矩阵\(a\)为系数矩阵, \(F\) 为关系矩阵,则有 \(f_n*A=F_{n+1}\)

\[\left[ \begin{matrix} f_n & f_{n+1} & f_{n+2}\\ \end{matrix} \right]\times\left[ \begin{matrix} a_{11} & a_{12} & a_{13}\\ a_{21} & a_{22} & a_{23} \\ a_{31} & a_{32} & a_{33} \end{matrix} \right]=\left[ \begin{matrix} f_{n+1} & f_{n+2} & f_{n+3}\\ \end{matrix} \right]\]

一般关系矩阵由问题给出,系数矩阵要根据关系矩阵的关系推导出来

例1. Fibonacci数列第n项

题目描述:

大家都知道,斐波那契数列是满足如下性质的一个数列:

\[F_n= \begin{cases} 1,\quad x\leq 2\\ F_{n-1} + F_{n-2}, \quad x\geq3 \end{cases}\]

请你求出 \(F_n mod\ 10^9 +7\) 的值。

输入格式:

一行一个正整数 \(n\)

输出格式:

输出一行一个整数表示答案。

输入1

5

输出1

5

输入2

10

输出2

55

数据范围

对于 60% 的数据,\(1≤n≤92\)
对于 100% 的数据,\(1≤n<2^{63}\)

点击查看代码
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr),cout.tie(nullptr)
#define int long long
#define mod1 1000000007
#define mod2 998244353

using namespace std;

typedef __int128 i128;

const int N = 2;

void mul(int c[][N], int b[][N], int a[][N])
{
    int t[N][N];
    memset(t, 0, sizeof t);
    for(int i = 0; i < N; i ++)
        for(int j = 0; j < N; j ++)
            for(int k = 0; k < N; k ++)
                t[i][j] = (t[i][j] + a[i][k] * b[k][j]) % mod1;
    memcpy(c, t, sizeof t);
}

void solve()
{
    int n;
    cin >> n; n --;
    int f[N][N] = {1, 1};
    int a[N][N] = {
        {0, 1},
        {1, 1}
    };
    while(n)
    {
        if(n & 1) mul(f, a, f);
        mul(a, a, a);
        n >>= 1;
    }
    cout << f[0][0] << '\n';
}

signed main()
{
    IOS; int _ = 1;
    while(_ --)
        solve();
    return _ ^ _;
}

例2. Fibonacci前n项和

题目描述:

佳佳对数学,尤其对数列十分感兴趣。

在研究完 \(Fibonacci\) 数列后,他创造出许多稀奇古怪的数列。

例如用 \(S(n)\) 表示 \(Fibonacci\)\(n\) 项和 modm 的值,即 \(S(n)=(F_1+F_2+…+F_n)mod\ m\),其中 \(F_1=F_2=1,F_i=F_{i−1}+F_{i−2}\)

可这对佳佳来说还是小菜一碟。

终于,她找到了一个自己解决不了的问题。

\(T(n)=(F_1+2F_2+3F_3+…+nF_n)mod\ m\) 表示\(Fibonacci\) 数列前 \(n\) 项变形后的和 \(mod\ m\) 的值。

现在佳佳告诉你了一个 \(n\)\(m\),请求出 \(T(n)\) 的值。

输入格式:

共一行,包含两个整数 \(n\)\(m\)

输出格式:

共一行,输出 \(T(n)\) 的值。

输入

5 5

输出

1

数据范围与解释

\(1≤n,m≤2^{31}−1\)
\(T(5)=(1+2×1+3×2+4×3+5×5)mod\ 5=1\)

点击查看代码
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr),cout.tie(nullptr)
#define int long long

using namespace std;

const int N = 4, M = 50;

int n, m;

void mul(int c[][N], int a[][N], int b[][N])
{
	static int t[N][N];
	memset(t, 0, sizeof t);
	for(int i = 0; i < N; i ++)
		for(int j = 0; j < N; j ++)
			for(int k = 0; k < N; k ++)
				t[i][j] = (t[i][j] + a[i][k] * b[k][j]) % m;
	memcpy(c, t, sizeof t);
}

void solve()
{
	cin >> n >> m;
	int f1[N][N] = {1, 1, 1, 0};
	int a[N][N] = {
		{0, 1, 0, 0},
		{1, 1, 1, 0},
		{0, 0, 1, 1},
		{0, 0, 0, 1},
	};
	int k = n - 1;
	while(k)
	{
		if(k & 1) mul(f1, f1, a);
		mul(a, a, a);
		k >>= 1;
	}
	cout << ((n * f1[0][2] - f1[0][3]) % m + m) % m << '\n';
}

signed main() {
    IOS; int _ = 1;
    // cin >> _;
    while(_ --)
        solve();
    return _ ^ _;
}

扩展欧几里得

1.扩展欧几里得

用于求解\(ax + by = gcd(a,b)\)的解,利用辗转相除法构造出x,y的通解

\(b = 0\)时,\(ax + by = a\),可令\(x = 1,y = 0\)

\(b \neq 0\)时,因

$gcd(a, b)$ $=$ gcd(b,a % b$)$

$bx^′$ + $($a % b$)y^′$ $=$ gcd(b,a % b$)$
$bx^′$ + $(a - {\lfloor a/b \rfloor}*b$)$y^′$ $=$ gcd(b,a % b$)$
$ay^′$ + $b(x^′ - \lfloor a/b \rfloor*y^′)$ $=$ gcd(b,a % b$) = gcd(a, b)$

$x = y^′, y = x^′ - \lfloor a/b \rfloor* y^′$

因此可以递归求解\(gcd(a,b)\),求出下一层的\(x^′和y^′\),再回溯过程用上式即可

2.一般的同余方程 ax + by = c

可设\(d = gcd(a, b)\),则其有解当且仅当\(d|c\)

求解:

用扩展欧几里得求出\(ax_0 + by_0 = d\)

\(a(x_0 * c/d) + b(y_0 * c/d) = c\)

故而特解为\(x^′ = x_0 * c/d\),\(y^′ = y_0 * c/d\)

通解 = 特解 + 齐次解

而齐次解即为方程\(ax + by = 0\)

故而通解为

\(x = x^′ + k * b/d,y = y^′ - k * a/d ,k \in z\)

若令\(t = b/d\),则对于\(x\)的最小非负整数解为\((x^′\)%\(t\) + t\()\)%\(t\)

3.应用:求解一次同余方程 ax ≡ b(mod m)

等价于求解

$ax = m * (-y) + b$
$ax + my = b$

有解条件为\(gcd(a,m)|b\),然后用扩展欧几里得求解即可

\(b = 1\)\(a与m\)互质,所求的\(x\)即为\(a\)的逆元

例1. 扩展欧几里得

题目描述:

给定 \(n\) 对正整数 \(a_i,b_i\),对于每对数,求出一组\(x_i,y_i\),使其满足 \(a_i×x_i+b_i×y_i=gcd(a_i,b_i)\)

输入格式:

第一行包含整数 \(n\)
接下来 \(n\) 行,每行包含两个整数 \(a_i,b_i\)

输出格式:

输出共 \(n\) 行,对于每组 \(a_i,b_i\),求出一组满足条件的 \(x_i,y_i\),每组结果占一行。
本题答案不唯一,输出任意满足条件的 \(x_i,y_i\) 均可。

输入

2
4 6
8 18

输出

-1 1
-2 1

数据范围

\(1≤n≤10^5,1≤a_i,b_i≤2×10^9\)

点击查看代码
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr),cout.tie(nullptr)
#define int long long

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;
}

void solve()
{
    int a, b, x, y;
    cin >> a >> b;
    int d = exgcd(a, b, x, y);
    cout << x << ' ' << y << '\n';
}

signed main()
{
    IOS;
    int _ = 1;
    cin >> _;
    while(_ --)
        solve();
    return 0;
}

例2. 求解同余方程

题目描述:

给定 \(n\) 组数据 \(a_i,b_i,m_i\),对于每组数求出一个\(x_i\),使其满足 \(a_i×x_i≡b_i(mod m_i)\),如果无解则输出 impossible

输入格式:

第一行包含整数 \(n\)
接下来 \(n\) 行,每行包含一组数据 \(a_i,b_i,m_i\)

输出格式:

输出共 \(n\) 行,每组数据输出一个整数表示一个满足条件的 \(x_i\),如果无解则输出impossible
每组数据结果占一行,结果可能不唯一,输出任意一个满足条件的结果均可。
输出答案必须在 \(int\) 范围之内。

输入

2
2 3 6
4 3 5

输出

impossible
-3

数据范围

\(1≤n≤10^5 ,1≤a_i,b_i,m_i≤2×10^9\)

点击查看代码
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr),cout.tie(nullptr)
#define int long long

using namespace std;

using LL = long long;

int n, x, y;

int exgcd(int a, int b, int &x, int &y)
{
    if(!b)
    {
        x = 1, y = 0;
        return a;
    }
    int d = exgcd(b, a % b, x, y);
    int z = x;
    x = y, y = z - a / b * y;
    return d;
}

void solve()
{
    int a, b, m;
    cin >> a >> b >> m;
    int d = exgcd(a, m, x, y);
    if(b % d) cout << "impossible\n";
    else
    {
        x = (LL) x * b / d % m;
        cout << x << endl;
    }
}

signed main()
{
    IOS;
    int _;
    cin >> _;
    while(_ --)
        solve();
    return _ ^ _;
}

例3. 青蛙的约会

题目描述:

两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。

我们把这两只青蛙分别叫做青蛙 A 和青蛙 B,并且规定纬度线上东经 0 度处为原点,由东往西为正方向,单位长度 1 米,这样我们就得到了一条首尾相接的数轴。设青蛙 A 的出发点坐标是 x,青蛙 B 的出发点坐标是 y。青蛙 A 一次能跳 m 米,青蛙 B 一次能跳 n 米,两只青蛙跳一次所花费的时间相同。纬度线总长 L 米。现在要你求出它们跳了几次以后才会碰面。

输入格式:

输入只包括一行五个整数 \(x,y,m,n,L\)

输出格式:

输出碰面所需要的次数,如果永远不可能碰面则输出一行一个字符串 Impossible

输入

1 2 3 4 5

输出

4

数据范围

对于 100% 的数据,$1≤x\neq y≤2×10^9 ,1≤m,n≤2×10^9 ,1≤L≤2.1×10^9 $。

点击查看代码
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr),cout.tie(nullptr)
#define int long long

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;
}

void solve()
{
    int a, b, m, n, l;
    cin >> a >> b >> m >> n >> l;
    int x, y;
    int d = exgcd(n - m, l, x, y);
    if((a - b) % d) cout << "Impossible\n";
    else
    {
        int s = (a - b) / d * x;
        int t = abs(l / d);
        cout << (s % t + t) % t << '\n';
    }
}

signed main()
{
    IOS; int _ = 1;
    // cin >> n;
    while(_ --)
        solve();
    return _ ^ _;
}

中国哦剩余定理(孙子定理)

\[\begin{cases} x\equiv a_1(mod\ m_1)\\ x\equiv a_2(mod\ m_2)\\ .\\ .\\ ·\\ x\equiv a_n(mod\ m_n)\\ \end{cases} \]

\[令 \begin{cases} M=m_1m_2m_3...m_n\\ M_i=\frac{M}{m_i}\\ t_i是M_i关于m_i的逆元,即M_it_i\equiv 1(mod\ m_i)\\ \end{cases} \]

\[\sum_{i = 1}^{n}a_iM_it_i \]

1. 将式子等价转换

对于每两个式子(考虑将其合并):

\(x≡m_1(mod\ a_1)\)

\(x≡m_2(mod\ a_2)\)

则有:

\(x=k_1∗a_1+m_1\)

\(x=k_2∗a_2+m_2\)

进而:

\(k_1∗a_1+m_1=k_2∗a_2+m_2\)

\(k_1∗a_1−k_2∗a_2=m_2−m_1\)

即:

\(k_1∗a_1+k_2∗(−a_2)=m_2−m_1\)

也就是需要找到一个最小的 \(k_1,k_2\),使得等式成立(因为要求x最小,而 \(a\)\(m\) 都是正数)。

2. 用扩展欧几里得算法找出一组解

已知 \(a_1,m_1,a_2,m_2\),可以用扩展欧几里得算法算出一个 \(k_1^′,k_2^′\) 使得:

\(k_1^′∗a_1+k_2^′∗(−a_2)=gcd(a_1,−a_2)\)

无解判断:

\(gcd(a_1,−a_2)∤(m_2−m_1)\),则无解。

\(d=gcd(a_1,−a_2),y=(m_2−m_1)d\)

依上文,只需让k1,k2分别扩大y倍,则可以找到一个k1,k2满足①式:

\(k_1=k_1^′∗y,k_2=k_2^′∗y\)

3. 找到最小正整数解

已知性质:

\(k_1=k_1+k∗\frac{a_2}d\)
\(k_2=k_2+k∗\frac{a_1}d\)

\(k\) 为任意整数,这时新的 \(k_1,k_2\) 仍满足①式。

证明:

将新的 \(k_1,k_2\) 带入式子得:

\((k_1+k∗\frac{a_2}d)∗a_1+(k_2+k∗\frac{a_1}d)∗(−a_2)=m_2−m_1\)

裂项,拆负号化简:

\(k_1∗a_1+k∗\frac{a_2∗a_1}d+k_2∗(−a_2)+k∗\frac{a_1∗(−a_2)}d=m_2−m_1\)

\(k_1∗a_1+k_2∗(−a_2)+k∗\frac{a_2∗a_1}d−k∗\frac{a_1∗a_2}d=m_2−m_1\)

\(k_1∗a_1+k_2∗(−a_2)=m_2−m_1\)

这个式子和①是一样的,因 成立,故此式也成立。

要找一个最小的非负整数解,只需让

\(k_1=k_1\% abs(\frac{a_2}d)\)
\(k_2=k_2\% abs(\frac{a_1}d)\)

即可找到当前最小的 \(k_1,k_2\) 的解,即此时的 \(k\)\(0\)

因为不知道 \(\frac{a_2}d\) 的正负性,在原基础上要尽量减多个 \(abs(\frac{a_2}d)\),使其为正整数且最小。

4. 等效替代:

式带入

新的x为:

\(x=(k_1+k∗\frac{a_2}d)∗a_1+m_1\)

\(=k_1∗a_1+m_1+k∗\frac{a_2∗a_1}d\)

\(=k_1∗a_1+m_1+k∗lcm(a_1,a_2)\)

这只是前两个式子得最小k,有可能遇到下一个式子后面被迫要扩大

中,设\(a_0=lcm(a_1,a_2),m_0=k_1∗a_1+m_1\)

则:

\(=k∗a_0+m_0\)

这个形式与一开始分解的形式特别像

假设之后又来了一个 \(a_3,m_3\),只需要继续找:

\(x=k∗a_0+m_0=k_3∗(−a_3)+m_3\) ,那么问题又回到了第一步。

5. 总结

此做法相当于每次考虑合并两个式子,将这n个式子合并 \(n−1\) 次后变为一个式子。最后剩下的式子就满足我们的答案。

注意:

(1)\(lcm(a_1,a_2)\)\(\%\frac{a_2}d\),需要取绝对值。又因为 \(d=gcd(a_1,−a_2)\),我们不知道a1的正负性(可能是上一步推过来的)。

(2)\(\%\frac{a_2}d\),需要取绝对值, 膜负数的话,不会取到正解;

例1. 表达整数的奇怪方式

题目描述:

给定 \(2n\) 个整数 \(a_1,a_2,…,a_n\)\(m_1,m_2,…,m_n\),求一个最小的非负整数 \(x\),满足 \(∀i∈[1,n],x≡m_i(mod a_i)\)

输入格式:

\(1\) 行包含整数 \(n\)

\(2…n+1\) 行:每 \(i+1\) 行包含两个整数 \(a_i\)\(m_i\),数之间用空格隔开。

输出格式:

输出最小非负整数 \(x\),如果 \(x\) 不存在,则输出 \(−1\)
如果存在 \(x\),则数据保证 \(x\) 一定在 \(64\) 位整数范围内。

输入

2
8 7
11 9

输出

31

数据范围

\(1≤a_i≤2^{31}−1 ,0≤m_i<a_i ,1≤n≤25\)

点击查看代码
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr),cout.tie(nullptr)
#define int long long

using namespace std;

int n;

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 inline mod(int a, int b)
{
	return ((a % b) + b) % b;
}

void solve()
{
    cin >> n;
	int a1, m1;
	cin >> a1 >> m1;
	for(int i = 1; i < n; i ++)
	{
		int a2, m2, k1, k2;
		cin >> a2 >> m2;
		int d = exgcd(a1, -a2, k1, k2);
		if((m2 - m1) % d)
		{
			puts("-1");
			return ;
		}
		k1 = mod(k1 * (m2 - m1) / d, abs(a2 / d));
		m1 = k1 * a1 + m1;
		a1= abs(a1 / d * a2);
	}
	cout << m1 << endl;
}

signed main()
{
    IOS; 
    int _ = 1;
    // cin >> _;
    while(_ --)
        solve();
    return _ ^ _;
}

例2. 曹冲养猪

题目描述:
自从曹冲搞定了大象以后,曹操就开始琢磨让儿子干些事业,于是派他到中原养猪场养猪,可是曹冲很不高兴,于是在工作中马马虎虎,有一次曹操想知道母猪的数量,于是曹冲想狠狠耍曹操一把。

举个例子,假如有 \(16\) 头母猪,如果建了 \(3\) 个猪圈,剩下 \(1\) 头猪就没有地方安家了;如果建造了 \(5\) 个猪圈,但是仍然有 \(1\) 头猪没有地方去;如果建造了 \(7\) 个猪圈,还有 \(2\) 头没有地方去。

你作为曹总的私人秘书理所当然要将准确的猪数报给曹总,你该怎么办?

输入格式:

第一行包含一个整数 \(n\),表示建立猪圈的次数;

接下来 \(n\) 行,每行两个整数 \(a_i,b_i\),表示建立了 \(a_i\) 个猪圈,有 \(b_i\) 头猪没有去处。

你可以假定 \(a_i,a_j\) 互质。

输出格式:

输出仅包含一个正整数,即为曹冲至少养猪的数目。

输入

3
3 1
5 1
7 2

输出

16

数据范围

\(1≤n≤10 ,1≤b_i≤a_i≤1100000\)
所有\(a_i\)的乘积不超过 \(10^{18}\)

点击查看代码
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr),cout.tie(nullptr)
#define int long long

using namespace std;

const int N = 15;

int m[N], a[N];
int n;

void exgcd(int a, int b, int &x, int &y)
{
    if(!b) x = 1, y = 0;
    else
    {
        exgcd(b, a % b, y, x);
        y -= a / b * x;
    }
}

void solve()
{
    cin >> n;
    int M = 1;
    for(int i = 0; i < n; i ++)
    {
        cin >> m[i] >> a[i];
        M *= m[i];
    }
    int ans = 0;
    for(int i = 0; i < n; i ++)
    {
        int mi = M / m[i];
        int ti, y;
        exgcd(mi, m[i], ti, y);
        ans += mi * a[i] * ti;
    }
    cout << (ans % M + M) % M << endl;
}

signed main()
{
    IOS;
    int _ = 1;
    // cin >> _;
    while(_ --)
        solve();
    return _ ^ _;
}
posted @ 2023-10-03 22:25  chfychin  阅读(65)  评论(0)    收藏  举报