裴蜀定理
裴蜀定理(贝祖定理):如果 \(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\) 为例:

除了最后一列,每一列最小的可被表示的价值实际上是 \(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;
}

浙公网安备 33010602011771号