把 k 位系统的循环步数写成线性同余方程 Ct ≡ B-A (mod 2^k),用扩展欧几里得判无解并求最小非负解。
OJ: roj
题目 ID: 1640
难度:普及+/提高-
标签:扩展欧几里得线性同余方程数论
创建: 2026-09-30 23:32
更新: 2026-10-07 13:50
形式化题目
在 FOREVER。
多组数据,每组一行四个整数 0 0 0 0 结束。
样例解释(以第四组 3 4 2 16 为例,变量每轮加 2,奇偶性不变,永远到不了偶数 4):
| 轮数 |
0 | 1 | 2 | 3 | 4 | … |
|---|---|---|---|---|---|---|
| 变量值 | 3 | 5 | 7 | 9 | 11 | … |
值始终为奇数,与 FOREVER。
正解
思路
朴素做法:直接模拟,变量从
优化推导:把"第
这是一个标准的线性同余方程,循环次数就是它的最小非负整数解
-
有解判定:方程有解当且仅当
。理由: 在模 意义下恒为 ,所以右边必须也是 的倍数。无解即序列永远不经过 → 输出 FOREVER。 -
求最小解:有解时,由扩展欧几里得得一组
满足 。两边同乘 得 ,所以 是一个特解。同余方程的全部解在模 意义下唯一(两个解之差能被 整除),故最小非负解为
用样例 3 7 2 16 走一遍求解链(方程
g = gcd(2, 65536) = 2,g | 4 → 有解
扩展欧几里得:2·1 + 65536·0 = 2 → x = 1
t = 1 · (4/2) mod (65536/2) = 2 → 输出 2而 7 3 2 16 时
实现要点:Python 大整数无中间乘法溢出问题(C++ 需用 long long 并小心 zip(it, it, it, it) 每次消费一行四个整数,遇 0 0 0 0 停止。
代码
Python 版:
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-30 23:35
# update_at: 2026-09-30 23:35
import sys
def ext_gcd(a: int, b: int) -> tuple[int, int, int]:
"""返回 (g, x, y) 满足 a*x + b*y = g = gcd(a, b)。"""
if b == 0:
return a, 1, 0
g, x, y = ext_gcd(b, a % b)
return g, y, x - (a // b) * y
def loop_times(a: int, b: int, c: int, k: int) -> str:
"""k 位系统里循环几次到达 B:解同余方程 c*t ≡ B-A (mod 2^k) 的最小非负 t。"""
mod = 1 << k
g, x, _ = ext_gcd(c, mod) # c 在 mod 下的逆相关系数
diff = b - a
if diff % g:
return "FOREVER" # gcd 不整除差值 → 无解 → 死循环
period = mod // g # 解在模 period 意义下唯一
return str(x * (diff // g) % period)
def solve() -> None:
nums = iter(map(int, sys.stdin.buffer.read().split()))
out: list[str] = []
for a, b, c, k in zip(nums, nums, nums, nums): # 同一迭代器 zip 四次 = 每次取一行四个数
if a == b == c == k == 0:
break
out.append(loop_times(a, b, c, k))
print('\n'.join(out))
if __name__ == "__main__":
solve()C++ 版(同一算法):
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-06 00:12
* update_at: 2026-10-06 00:12
*/
#include <iostream>
using namespace std;
typedef long long ll;
// 扩展欧几里得:求 g = gcd(a, b),并给出 x, y 使 a*x + b*y = g
ll ext_gcd(ll a, ll b, ll &x, ll &y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
ll x1, y1;
ll g = ext_gcd(b, a % b, x1, y1);
x = y1;
y = x1 - (a / b) * y1;
return g;
}
int main() {
ll a, b, c, k;
while (cin >> a >> b >> c >> k) {
if (a == 0 && b == 0 && c == 0 && k == 0) {
break;
}
ll mod = 1LL << k; // k 位系统的模数 2^k
ll x, y;
ll g = ext_gcd(c, mod, x, y); // g = gcd(C, 2^k)
ll diff = b - a; // 同余方程右端 Ct ≡ B - A (mod 2^k)
if (diff % g != 0) {
cout << "FOREVER" << "\n"; // gcd 不整除差值,永远到不了 B
continue;
}
ll period = mod / g; // 解在模 period 意义下唯一
// 把 x、diff/g 都化到 [0, period),再用 __int128 相乘防止溢出
x = ((x % period) + period) % period;
ll factor = ((diff / g) % period + period) % period;
__int128 prod = (__int128)x * factor;
ll ans = prod % period; // 最小非负解
cout << ans << "\n";
}
return 0;
}复杂度
每组数据一次扩展欧几里得,时间
总结
- 关键转化:
位系统里的"循环几次到达 "= 线性同余方程 的最小非负解,无解即死循环。 - 求解套路:扩展欧几里得求
与系数 ; 输出 FOREVER,否则答案为。 - 边界自检:
得 ; 时 , 输出 、否则 FOREVER,与"变量不动"的语义一致。