数学知识--数论
目录
质数
素数定理:小于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可以唯一分解成有限个质数的乘积:
应用
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\)
4. 求组合数
在不取 \(mod\) 的情况下,用唯一分解求组合数:
试除法求所有约数\(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\)互质,有
又有$$b^{m-1}\equiv 1(mod\ m)$$
故 \(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}\)
可利用矩阵的幂次性结合快速幂加速求解一些有关系的数据
令矩阵\(a\)为系数矩阵, \(F\) 为关系矩阵,则有 \(f_n*A=F_{n+1}\)
一般关系矩阵由问题给出,系数矩阵要根据关系矩阵的关系推导出来
例1. Fibonacci数列第n项
题目描述:
大家都知道,斐波那契数列是满足如下性质的一个数列:
请你求出 \(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)\),求出下一层的\(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)
等价于求解
有解条件为\(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 _ ^ _;
}
中国哦剩余定理(孙子定理)
则
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 _ ^ _;
}
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号