AcWing 204. 表达整数的奇怪方式

AcWing 204. 表达整数的奇怪方式

一、题目描述

给定 2n 个整数 a1,a2,,anm1,m2,,mn求一个最小的非负整数 x,满足 i[1,n],xmi(mod ai)

输入格式
1 行包含整数 n

2n+1 行:每 i+1 行包含两个整数 aimi,数之间用空格隔开。

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

数据范围
1ai2311,0mi<ai

1n25

输入样例:

2
8 7
11 9

输出样例:

31

二、解题思路

中国剩余定理

作用:求n组线性同余方程的通解

使用前提:对于i[1,n],xmi(mod ai),其中若m1,m2,,mn 两两互质,则可以使用 中国剩余定理 来求解x

形式: $$ \large (S) : \left{xa1(mod m1) xa2(mod m2) . . . xan(mod mn) \right.$$

公式

x=(i=1naitiMi)mod M

其中tiMi的逆元,Mi=M/mi(除了mi以外的乘积),M=m1m2m3...mn

扩展中国剩余定理

本题中并未说m1,m2,,mn之间两两互质,需要重新推导:

推导(按题目的来)
【一会m是除数,a是余数,一会又是m是余数,a是除数,真是太BT了,本来就不咋会,还故意扰乱我的思路!】

{xm1(mod a1)xm2(mod a2)...xmn(mod an)

先计算两个线性同余方程组的解,之后依此类推
① 可以写成 x=k1a1+m1
② 可以写成 x=k2a2+m2

③=④ ⟶k1a1+m1=k2a2+m2k1a1k2a2=m2m1

根据 裴蜀定理,当m2m1gcd(a1,a2)的倍数时,方程⑤有无穷组整数解

k1a1k2a2=gcd(a1,a2)=d可以用拓展欧几里得算法来解,即exgcd(a1,a2,k1,k2)

Q:k1a1k2a2=gcd(a1,a2)=d中的k1k2通解公式是什么?

结论

{k1=k1+ka2dk2=k2+ka1d

解释:如果我们知道一组特解(k1,k2),那么,可以通过任意整数倍k的缩放,使得k1缩放ka2d倍,k2缩放ka1d倍,得到的新k1,k2依然是方程的解。

证明:
k1的通解为s1,k2的通解为s2,k1的特解为k1,k2的特解为k2

解释:这么写方便些,不想再写什么k1,k2了,太麻烦~

则:

{a1s1a2s2=da1k1a2k2=d

d

{a1ds1a2ds2=1a1dk1a2dk2=1

a1d(s1k1)a2d(s2k2)=0

a1da2d>s1k1a2d

s1k1=ka2d>k1=k1+ka2d(s1k1)

k2=k2+ka1d

傻瓜问题a1da2d为什么互质?
:在方程组⑥中根据裴蜀定理的推论(a,b互质的充要条件是存在整数x,y使ax+by=1)从而得出它两互质.

到此证明完毕,回归正题,求解本题。

k1通解代入③得

x=k1a1+m1>x=(k1+ka2d)a1+m1>

x=ka2da1a1+k1a1+m1m1

a1=a2da1,m1=k1a1+m1,就和原来的方程一个样子,但化简掉了一个方程,问题变得简单些,只要不断的合并,最终就可以得到答案

依此迭代,经过n1次后可以将n个线性同余方程合并为一个方程,求最小解,只需最后(m1%a1+a1)%a1)即可

三、实现代码

#include <iostream>

using namespace std;
typedef long long LL;

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

LL n, a1, m1, a2, m2, k1, k2;
int main() {
    cin >> n;
    cin >> a1 >> m1; // 读入第一个方程
    n--;             // 共n-1个方程
    while (n--) {
        cin >> a2 >> m2;
        // 开始合并
        // a1*k1+(-a2)*k2=m2-m1

        // ① 求a1*k1+(-a2)*k2=gcd(a1,-a2)的解,视k1=x,k2=y
        LL d = exgcd(a1, -a2, k1, k2);
        if ((m2 - m1) % d) { // 如果不是0,则无解
            cout << -1;
            exit(0);
        }

        // ② 求 a1*k1+(-a2)*k2=m2-m1 的一组解,需要翻 (m2-m1)/d倍
        k1 *= (m2 - m1) / d;

        // ③ 求最小正整数解
        int t = abs(a2 / d);
        k1 = (k1 % t + t) % t;

        // ④ 更新a1和m1,准备下一轮合并
        m1 = k1 * a1 + m1;
        a1 = abs(a1 / d * a2);
    }
    // 输出,m1是一个解,不是最小整数解,需要模a1
    cout << (m1 % a1 + a1) % a1;
    return 0;
}
posted @   糖豆爸爸  阅读(285)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2016-10-08 工资系统解决办法
2016-10-08 控制其它程序
Live2D
点击右上角即可分享
微信分享提示