裴蜀定理

裴蜀定理(贝祖定理):如果 \(a,b\) 是不全为 \(0\) 的整数,一定存在整数 \(x, y\),满足 \(ax+by=gcd(a,b)\)

例如 \(4x+6y=2\),存在整数解 \(x=-1,y=1\),而 \(4x+6y=3\) 也就是 \(x=\dfrac{3-6y}{4}\) 无整数解。

裴蜀定理也可以理解为:\(ax\)\(by\) 在随意给定整数 \(x\)\(y\) 的情况下,能得到的最小的正整数差值是 \(gcd(a,b)\)

裴蜀定理的证明:设取整数 \(x_0, y_0\) 时,\(ax+by\) 的最小正整数为 \(s\),即 \(ax_0+by_0=s\)

  • 因为 \(gcd(a,b)|ax_0, gcd(a,b)|by_0\),所以 \(gcd(a,b)|s\)
  • \(a=qs+r \ (0 \le r < s)\),那么 \(r=a-qs=a-q(ax_0+by_0)=a(1-qx_0)+b(-qy_0)\),也就是说,\(r\) 也可以写成 \(ax+by\) 的形式。因为 \(s\) 已经是这种形式下的最小正整数了,而 \(r<s\),那么 \(r\) 只能是 \(0\) 了,所以 \(s|a\),同理 \(s|b\),所以 \(s|gcd(a,b)\)
  • 既然 \(gcd(a,b)|s\) 同时 \(s|gcd(a,b)\),那么 \(s = gcd(a,b)\)

习题:UVA408 Uniform Generator

解题思路

生成的数实际上就是 \(k \cdot step\)\(mod\) 取余的结果,其中 \(k=0,1,2,\dots\),这些数可以表示为 \(k \cdot step - p \cdot mod\),其中 \(p=0,1,2,\dots\),也就转化为了 \(step\)\(mod\) 的线性组合,要能够覆盖 \(0 \sim mod-1\) 之间的数,根据裴蜀定理,需要 \(step\)\(mod\) 互质。

参考代码
#include <cstdio>
int gcd(int x, int y) {
    return y == 0 ? x : gcd(y, x % y);
}
int main()
{
    int step, mod;
    while (~scanf("%d%d", &step, &mod)) {
        printf("%10d%10d    ", step, mod);
        printf("%s Choice\n\n", gcd(step, mod) == 1 ? "Good" : "Bad");
    }
    return 0;
}

推论 1:一定存在整数 \(x, y\),满足 \(ax+by=gcd(a,b) \times n\)

例如 \(4x+6y=8\),存在整数解 \(x=-4,y=4\)

推论 2:一定存在整数 \(X_1 \cdots X_n\),满足 \(\sum \limits_{i=1}^n A_i X_i = gcd(A_1, A_2, \cdots, A_n)\)

例如 \(4x_1 + 6x_2 + 2x_3 = 4\),存在整数解 \(x_1 = 1, x_2 = 0, x_3 = 0\)


习题:P4549 【模板】裴蜀定理

解题思路

即推论 2,注意系数 \(A_i\) 可以是负数,用辗转相除法求 \(gcd\) 时,如果参数代入负数,结果可能会返回负数,例如 \(gcd(8,-4)\) 求出来是 \(-4\),因此可以将系数的绝对值代入,确保所求的最大公约数为正数,这样并不会影响解的存在。

时间复杂度 \(O(n \log a)\)

参考代码
#include <cstdio>
const int N = 25;
int a[N];
int gcd(int x, int y) {
    return y == 0 ? x : gcd(y, x % y);
}
int main()
{
    int n; scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]); 
        if (a[i] < 0) a[i] *= -1;
    }
    int ans = a[1]; 
    for (int i = 2; i <= n; i++) ans = gcd(ans, a[i]);
    printf("%d\n", ans);
    return 0;
}

习题:Pagodas

解题思路

实际上最终可以修的宝塔的编号就是输入的 \(a\)\(b\) 的线性组合,根据裴蜀定理也就是 \(gcd(a,b)\) 的倍数。因此 \([1,n]\) 之间能修的宝塔的数量就是 \(\left\lfloor \dfrac{n}{gcd(a,b)} \right\rfloor\),一开始已经修好的两座宝塔不影响数量的奇偶性,这个数为奇数则先手必胜,偶数则后手必胜。

参考代码
#include <cstdio>
int gcd(int x, int y) {
    return y == 0 ? x : gcd(y, x % y);
}
void solve(int id) {
    printf("Case #%d: ", id);
    int n, a, b;
    scanf("%d%d%d", &n, &a, &b);
    int g = gcd(a, b);
    if (n / g % 2 == 1) printf("Yuwgna\n");
    else printf("Iaka\n");
}
int main()
{
    int t; scanf("%d", &t);
    for (int i = 1; i <= t; i++) solve(i);
    return 0;
}

习题:P3951 [NOIP 2017 提高组] 小凯的疑惑

解题思路

要求的是最大的表示不出来的价值,那么这个价值加一是能被表示出来的。

如果一个价值 \(n\) 能被表示出来,那么 \(n+b\) 一定可以被表示。

将价值按除以 \(b\) 的余数分成 \(b\) 组,以 \(a=3, \ b=7\) 为例:

image

除了最后一列,每一列最小的可被表示的价值实际上是 \(ka + 0b\) 的形式,由于 \(a\)\(b\) 互质,那么根据裴蜀定理,\(0a \sim (b-1)a\) 一定都覆盖得到,因此这些数中最大的那个就是 \((b-1)a\),而要求的是最大的不能被表示的,再减 \(b\) 即为答案。因此答案实际上就是 \(ab-a-b\)

参考代码
#include <cstdio>
int main()
{
    int a, b;
    scanf("%d%d", &a, &b);
    printf("%lld\n", 1ll * a * b - a - b);
    return 0;
}

习题:CF1260C Infinite Fence

解题思路

首先,只需要考虑 \(0\)\(lcm(r,b)\) 这一段,因为这之后就相当于对前面的重复。

不妨设 \(r \lt b\),其中 \(0\)\(lcm(r,b)\) 一定染 \(b\) 的颜色,因为距离这两个点最近的点都是染 \(r\) 的颜色的。

考虑两个 \(b\) 颜色的点中间最多能夹几个 \(r\) 颜色的点,这时考虑让内部第一个 \(r\) 颜色的点尽量靠左。

根据裴蜀定理,显然这个最靠左的 \(r\) 颜色的点和左边这个 \(b\) 颜色的点之间的最近距离是 \(gcd(r,b)\)

此时两个 \(b\) 颜色的点之间会夹 \(\left\lfloor \dfrac{b-1-gcd(r,b)}{r} \right\rfloor + 1\)\(r\) 颜色的点,判断其与 \(k\) 的大小关系即可。

参考代码
#include <cstdio>
#include <algorithm>
using std::swap;
int gcd(int x, int y) {
    return y == 0 ? x : gcd(y, x % y);
}
void solve() {
    int r, b, k; scanf("%d%d%d", &r, &b, &k);
    if (r > b) swap(r, b);
    printf("%s\n", (b - 1 - gcd(r, b)) / r + 1 >= k ? "REBEL" : "OBEY");
}
int main()
{
    int t; scanf("%d", &t);
    for (int i = 1; i <= t; i++) solve();
    return 0;
}

习题:P6476 [NOI Online #2 提高组] 涂色游戏

解题思路

跟上一题相比多了 \(k=1\) 的情况,注意特判。此时答案一定是 No

参考代码
#include <cstdio>
#include <algorithm>
using std::swap;
int gcd(int x, int y) {
    return y == 0 ? x : gcd(y, x % y);
}
void solve() {
    int p1, p2, k;
    scanf("%d%d%d", &p1, &p2, &k);
    if (p1 > p2) swap(p1, p2);
    if (k == 1) printf("No\n");
    else printf("%s\n", (p2 - 1 - gcd(p1, p2)) / p1 + 1 >= k ? "No" : "Yes");
}
int main()
{
    int t; scanf("%d", &t);
    for (int i = 1; i <= t; i++) solve();
    return 0;
}

习题:CF1982D Beauty of the mountains

解题思路

首先计算两种山峰高度的差值 \(D\),对于每个大小为 \(k \times k\) 的子矩阵,计算该子矩阵对不同类型山峰高度和的差值造成的影响。也就是说,对于每个子矩阵,计算其中 \(0\)\(1\) 的数量差,记作 \(d_i\)。这样的子矩阵一共有 \((n-k+1) \times (m-k+1)\) 个,可以借助二维前缀和来计算。

于是,相当于这样一个方程:$ \sum \limits c_i d_i = D$,其中 \(c_i\) 代表该子矩阵需要整体加减的数值。这个方程如果要有整数解需要满足 \(gcd(d_1, d_2, \cdots)\) 能整除 \(D\)

注意特判所有 \(d_i\) 都等于 \(0\)\(D=0\) 这两种特殊情况。

总的时间复杂度为 \(O(nm + \log A)\)

参考代码
#include <cstdio>
#include <cstdlib>
using ll = long long;
using std::abs;
const int N = 505;
int a[N][N], s[N][N];
char tp[N][N];
int gcd(int x, int y) {
    return y == 0 ? x : gcd(y, x % y);
}
void solve() {
    int n, m, k; scanf("%d%d%d", &n, &m, &k);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            scanf("%d", &a[i][j]);
    ll s0 = 0, s1 = 0;
    for (int i = 1; i <= n; i++) {
        scanf("%s", tp[i] + 1);
        for (int j = 1; j <= m; j++) {
            if (tp[i][j] == '0') s0 += a[i][j];
            else s1 += a[i][j];
            s[i][j] = s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1] + (tp[i][j] - '0');
        }
    }
    ll delta = abs(s0 - s1);
    if (delta == 0) {
        printf("YES\n"); return;
    }
    int g = 0;
    for (int i = k; i <= n; i++) {
        for (int j = k; j <= m; j++) {
            int c = s[i][j] - s[i][j - k] - s[i - k][j] + s[i - k][j - k];
            int d = k * k - c;
            int diff = abs(c - d);
            if (diff != 0) {
                if (g == 0) g = diff;
                else g = gcd(g, diff);
            }
        }
    }
    printf("%s\n", g != 0 && delta % g == 0 ? "YES" : "NO");
}
int main()
{
    int t;
    scanf("%d", &t);
    for (int i = 1; i <= t; i++) solve();
    return 0;
}
posted @ 2025-06-25 10:45  RonChen  阅读(128)  评论(0)    收藏  举报