题单 · 习题解析

洛谷 P2152 [SDOI2009] SuperGCD

★ 题面 20% 档 a,b ≤ 10¹⁸ ⇒ unsigned long long + 循环版**白拿 20 分**(那条线:最多装 19 位十进制);★★★ 100% 档是 **10¹⁰⁰⁰⁰(一万位)** ⇒ 关键一步是**把 mod 换掉** —— 高精度下取模是一次长除法,太贵 ⇒ 二进制 gcd:只用「减法、判奇偶、除以 2、乘以 2」,**没有乘法也没有除法**(压位基数 10⁸,一万位只要 1250 个 limb);★★ 它凭什么快:**两个奇数的差必是偶数** ⇒ 每轮至少有一个数减半 ⇒ O(log a) —— 实测一万位 ≈ 33219 个二进制位、只走 **23471 轮**(比值 **0.71**),这正是[第 40 章第 9 步那条界](/ch/40-gcd-euclid/)换了个形式;⚠⚠ 而「不提取因子 2」那一版**答案永远是对的** ⇒ 对拍原理上抓不到,只能数步数:一万位随机 23471 轮 vs **422493 步**(18 倍、0.94 秒),⚠ **而两数只差 1 那一档它直接跑不完** ⇒ 顶格随机会让你以为「只是有点慢」

原题:洛谷 P2152出自 第 40 章 GCD、LCM 与欧几里得算法 的题单题面本地存档:2026-09-01
⚠ 先自己写一遍,再往下看

这一页不是标准答案,是一条阶梯:把大多数人真实会写出的第一版摆出来, 看它错在哪、慢在哪,再一步一步改。所以它对「已经自己动手撞过一次墙」的人最有用。
还没写过的话,先去写 —— 写出来的哪怕是错的,也比直接看这一页收获大。

0题目原文(本地存了一份)

为什么要在这儿抄一份题面

原题在洛谷上(页头有链接)。别人的网站不归我们管,打不开、改版、题号调整都可能发生。 所以每个解析页都把题面转录一份存在本地,跟着仓库一起进版本库。

转录自洛谷 P2152,日期见页头。两边不一致时信原站。

题目描述

Sheng bill 有着惊人的心算能力,甚至能用大脑计算出两个巨大的数的最大公约数! 因此他经常和别人比赛计算最大公约数。有一天 Sheng bill 很嚣张地找到了你,并要求和你比赛, 但是输给 Sheng bill 岂不是很丢脸!所以你决定写一个程序来教训他。

输入格式

共两行,第一行一个整数 a,第二行一个整数 b

输出格式

一行,表示 ab 的最大公约数。

数据范围

  • 对于 20% 的数据,有 0 < a, b ≤ 10¹⁸
  • 对于 100% 的数据,有 0 < a, b ≤ 10¹⁰⁰⁰⁰

时限 1 秒,内存 125 MB。(SDOI2009)

输入输出样例

输入

12
54

输出

6

gcd(12, 54) = 6

1★ 先把题面那两档乘一遍 —— 20 分是白给的

p2152Small.cpp★ 20% 那一档:a, b ≤ 10¹⁸ ⇒ unsigned long long 装得下,一行 while
// P2152 第 ① 版:题面 20% 那一档(a, b ≤ 10¹⁸)—— 直接套第 40 章正文那份欧几里得
//
// ★ 题面自己把这一档写好了:「对于 20% 的数据,0 < a, b ≤ 10¹⁸」
// ⇒ `unsigned long long` 装得下(上限 1.8 × 10¹⁹),一行 while 就完事 ⇒ **稳拿 20 分**。
//
// ⚠ 而 100% 那一档是 **10¹⁰⁰⁰⁰**(一万位)——
// 任何内置整数类型都装不下,只能写高精度(见 p2152.cpp)。
//
// ⚠ 这一版在大数据上会读进一个截断的值,答案自然是错的 —— 它只负责那 20 分。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string sa, sb;
if (!(cin >> sa >> sb)) return 0;
// ⚠ 超过 19 位就装不下了:这一版只对 20% 那一档负责
unsigned long long a = 0, b = 0;
for (char c : sa) a = a * 10 + (unsigned)(c - '0');
for (char c : sb) b = b * 10 + (unsigned)(c - '0');
while (b) { unsigned long long t = a % b; a = b; b = t; }
cout << a << '\n';
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果

题面把这一档写得明明白白:「对于 20% 的数据,0 < a, b ≤ 10¹⁸」—— unsigned long long 的上限是 1.8 × 10¹⁹装得下第 40 章正文那份循环版原样抄过来就是 20 分

⚠ 而 100% 那一档是 10¹⁰⁰⁰⁰ —— 一万位。 任何内置整数类型都装不下,只能写高精度。 ★ 这条线是一句算术:unsigned long long 最多装 19 位十进制,从第 20 位起就废了。

⚠⚠ 可「废了」不等于「一测就被抓」—— 200 位那一档它只被抓不到三成

我本来以为这一版在 200 位的数据上会全军覆没。实测 60 轮只被抓 17 轮

数一数就清楚了:

200 位那一档,60 轮里 实测
正解答 1(两数互质)的轮数 34
把两个数截断到 2⁶⁴ 之后,gcd 是 1 的轮数 40
⇒ 两版答案不同的轮数 17

★★★ 随机的两个大数本来就往往互质 —— 正解答 1,而截断之后的两个数也往往互质两版一起输出 1,对拍记「通过」,验的却是零。 ⇒ 「一致有两种:都算对了,和都没算」的又一次现场, 而这次它出现在一个「一眼就该错」的版本上。

2★★★ 关键一步:把 mod 换掉

⚠ 高精度下,gcd(a, b) = gcd(b, a mod b) 里的那个 mod 太贵

第 40 章第 6 步那条递推式的核心是取模, 而取模在高精度下是一次长除法 —— 一万位除以一万位,写起来长、跑起来更贵。

★ 换一条只用「减法、判奇偶、除以 2」的路 —— 二进制 gcd(Stein 算法):

情况 怎么走 为什么
ab 都是偶数 gcd = 2 · gcd(a/2, b/2) 公共的那个 2 先提出来记账
a 偶、b gcd(a, b) = gcd(a/2, b) 2 不是公因子,直接扔掉
ab 都是奇数 大的减去小的,gcd 不变 更相减损,而两个奇数的差必是偶数

★★ 最后那半句是整件事的关键:差必是偶数 ⇒ 下一轮立刻能除以 2每一轮至少有一个数被减半 ⇒ 总轮数 O(log a + log b)

⇒ 这正是第 40 章第 9 步那条「每两步至少减半」的界换了个形式又出现了一次

p2152.cpp正解:压位高精度(基数 10⁸)+ 二进制 gcd —— 一万位 88 毫秒
// P2152 正解 —— 压位高精度 + 二进制 gcd(Stein 算法)
//
// ============ ★★★ 为什么不能照搬第 40 章那份欧几里得 ============
//
// `gcd(a, b) = gcd(b, a mod b)` 里的那个 **mod,在高精度下是一次长除法** ——
// 一万位除以一万位,写起来长、跑起来更贵。
// ⇒ 换成**只用「减法、判奇偶、除以 2」**的二进制 gcd:
//
// · a、b 都是偶数 ⇒ gcd = 2 · gcd(a/2, b/2) (公共的 2 先提出来记账)
// · a 偶 b 奇 ⇒ gcd(a, b) = gcd(a/2, b) (2 不是公因子,扔掉)
// · a、b 都是奇数 ⇒ gcd(a, b) = gcd(|a−b|, min) (更相减损,而差必是偶数)
//
// ★★ 而[第 40 章第 9 步](/ch/40-gcd-euclid/)那条「每两步至少减半」的界,
// 在这里换了个形式又出现了一次:**每一轮至少有一个数被除以 2**
// ⇒ 总轮数 O(log a + log b) ≈ 一万位 × 3.32 ≈ **11 万轮**,每轮 O(位数)。
//
// ============ 高精度怎么存 ============
//
// 压位:每个 uint32 存 8 位十进制(基数 10⁸),一万位 ⇒ 1250 个 limb。
// 要的操作只有四个:比较、减法、除以 2、乘以 2 —— **没有除法,也没有乘法**。
//
// ⚠ 「除以 2」要从高位往低位扫(借位往下带),「乘以 2」要从低位往高位扫(进位往上带)。
#include <bits/stdc++.h>
using namespace std;
static const unsigned BASE = 100000000u; // 10⁸
static const int W = 8;
struct Big {
vector<unsigned> d; // 低位在前
void trim() { while (d.size() > 1 && d.back() == 0) d.pop_back(); }
bool isZero() const { return d.size() == 1 && d[0] == 0; }
bool isEven() const { return (d[0] & 1u) == 0; }
};
static Big fromString(const string& s) {
Big r;
for (int i = (int)s.size(); i > 0; i -= W) {
int l = max(0, i - W);
r.d.push_back((unsigned)stoul(s.substr(l, i - l)));
}
if (r.d.empty()) r.d.push_back(0);
r.trim();
return r;
}
static string toString(const Big& a) {
string s = to_string(a.d.back());
for (int i = (int)a.d.size() - 2; i >= 0; i--) {
string t = to_string(a.d[i]);
s += string(W - t.size(), '0') + t;
}
return s;
}
/** a 和 b 比大小:>0 / 0 / <0 */
static int cmp(const Big& a, const Big& b) {
if (a.d.size() != b.d.size()) return a.d.size() < b.d.size() ? -1 : 1;
for (int i = (int)a.d.size() - 1; i >= 0; i--)
if (a.d[i] != b.d[i]) return a.d[i] < b.d[i] ? -1 : 1;
return 0;
}
/** a -= b(要求 a ≥ b) */
static void sub(Big& a, const Big& b) {
long long borrow = 0;
for (size_t i = 0; i < a.d.size(); i++) {
long long cur = (long long)a.d[i] - borrow - (i < b.d.size() ? (long long)b.d[i] : 0);
if (cur < 0) { cur += BASE; borrow = 1; } else borrow = 0;
a.d[i] = (unsigned)cur;
}
a.trim();
}
/** a /= 2 —— 从高位往低位,借位往下带 */
static void halve(Big& a) {
unsigned rem = 0;
for (int i = (int)a.d.size() - 1; i >= 0; i--) {
unsigned long long cur = (unsigned long long)rem * BASE + a.d[i];
a.d[i] = (unsigned)(cur >> 1);
rem = (unsigned)(cur & 1ull);
}
a.trim();
}
/** a *= 2 —— 从低位往高位,进位往上带 */
static void twice(Big& a) {
unsigned carry = 0;
for (size_t i = 0; i < a.d.size(); i++) {
unsigned long long cur = (unsigned long long)a.d[i] * 2 + carry;
a.d[i] = (unsigned)(cur % BASE);
carry = (unsigned)(cur / BASE);
}
if (carry) a.d.push_back(carry);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string sa, sb;
if (!(cin >> sa >> sb)) return 0;
Big a = fromString(sa), b = fromString(sb);
long long shift = 0;
while (a.isEven() && b.isEven()) { halve(a); halve(b); shift++; } // 公共的 2 记账
while (!a.isZero()) {
while (a.isEven()) halve(a); // a 的 2 不是公因子,扔掉
while (b.isEven()) halve(b);
if (cmp(a, b) >= 0) sub(a, b); else sub(b, a); // 两个奇数相减,差必偶
}
while (shift--) twice(b); // 把记的账还回去
cout << toString(b) << '\n';
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果
★ 高精度只要四个操作 —— 没有除法,也没有乘法

压位:每个 unsigned8 位十进制(基数 10⁸)⇒ 一万位只要 1250 个 limb。 而二进制 gcd 用到的只有:比较、减法、除以 2、乘以 2

⚠ 两个方向别搞反:「除以 2」要从高位往低位扫(借位往下带), 「乘以 2」要从低位往高位扫(进位往上带)。

3⚠ 那「不提取因子 2」会怎样 —— 而对拍永远告诉不了你

p2152Sub.cpp⚠ 只用更相减损(不提因子 2)—— 答案永远对,坏的只有复杂度
★★★ 它的答案永远是对的 ⇒ 只能数步数

更相减损(gcd(a,b) = gcd(a−b, b))本身是正确的算法 ⇒ 拿它去对拍,一万轮也抓不到第 37 章 P3378 那条: 「答案对但跑不完」这一类,样例和对拍都是聋的)。

⇒ 换一把机器无关的尺子(p2152Count.cpp,⚠ 减法版超过 300 万步就记成「跑不完」):

数据 二进制 gcd 轮数 只做减法 步数 二进制 gcd 毫秒 只做减法 毫秒
200 位随机 468 5 985(12.8 倍) 0 0
一万位随机 23 471 422 493(18.0 倍 88 941
★ 一万位、两数只差 1 23 512 跑不完(> 300 万步) 93 ——

★★★ 最后一行才是它真正的死法:ab 只差一点点的时候, 每次减法只让数小一点点 ⇒ 最坏要减 a / b 次。 一万位的数,这个次数比宇宙里的原子还多。 ⚠ 而顶格随机那一档它只慢 18 倍(0.94 秒,时限 1 秒)—— ⇒ 顺手造一组顶格随机数据跑一遍,你会以为它「只是有点慢」。「顶格 ≠ 最坏」在这一章的又一次。)

★ 而正解那条界是实打实的:一万位十进制 ≈ 33 219 个二进制位, 二进制 gcd 只走了 23 471 轮 ⇒ 轮数 / 位数 = 0.71 ⇒ 「每轮至少减半一次」成立。

p2152Gen.cpp(五个档位)★ 档 3 就是「两数只差一点」那一档 —— 专门卡减法版
// P2152 的生成器:./p2152Gen 种子 [档位]
//
// ★ 每个版本靠什么现形:
// · p2152Small(用 unsigned long long 读)→ 只要**数超过 19 位**就完了。
// ⇒ 档 0(20 位以内)是精确的 0,档 1 起必错。
// · p2152Sub(更相减损,不提取因子 2)→ 它**答案永远是对的**,坏的只有复杂度
// ⇒ [对拍原理上抓不到它](/sol/p3378/),只能数步数 / 看秒表(见 p2152Count.cpp)。
//
// 档位:
// 0 ★ 题面 20% 那一档:a、b ≤ 10¹⁸(能和 p2152Small 对拍)
// 1 中等:约 200 位
// 2 ★ 顶格:约 10000 位(题面 100% 那一档)
// 3 ⚠⚠ **专门卡「不提因子 2」那一版**:a 和 b 差得很小(b = a − 小量)
// ⇒ 更相减损每次只减掉一点点,而二进制 gcd 照样每轮减半
// 4 ★ 两个数含大量公共的 2(都乘上 2^k)—— 逼出「先把公共的 2 提出来」那一步
//
// ⚠ 题面 0 < a, b ⇒ 不能造出 0。
// ⚠ rng() 一律先落到具名变量再传参。
#include <bits/stdc++.h>
using namespace std;
static string randDigits(mt19937_64& rng, int len) {
string s;
s += char('1' + (int)(rng() % 9ull));
for (int i = 1; i < len; i++) s += char('0' + (int)(rng() % 10ull));
return s;
}
int main(int argc, char** argv) {
unsigned seed = argc > 1 ? (unsigned)atoi(argv[1]) : 1;
int mode = argc > 2 ? atoi(argv[2]) : 0;
mt19937_64 rng(seed * 1000003ull + 20260901ull);
if (mode == 0) {
unsigned long long a = 1 + rng() % 1000000000000000000ull;
unsigned long long b = 1 + rng() % 1000000000000000000ull;
printf("%llu\n%llu\n", a, b);
return 0;
}
int len = (mode == 1) ? 200 : 10000;
if (mode == 3) {
// a 和 b 只差一点点 ⇒ 更相减损要减很多次
string sa = randDigits(rng, len);
printf("%s\n", sa.c_str());
// b = a - 2(保持同奇偶时差为偶,减法版会一直小步走)
string sb = sa;
int i = (int)sb.size() - 1;
while (i >= 0 && sb[i] == '0') { sb[i] = '9'; i--; }
if (i >= 0 && sb[i] > '1') sb[i]--; else if (i >= 0) sb[i] = '1';
printf("%s\n", sb.c_str());
return 0;
}
if (mode == 4) {
// 两个数都含大量公共的 2:造 odd * 2^k 的十进制串不方便,
// 改成都乘以 1024(= 2¹⁰)的若干次 —— 用末尾补 0 近似不行(那是 10 的幂,含 2 也含 5)
// ⇒ 直接让两个数都是偶数且低位大量相同:取同一个前缀 + 不同后缀,再各乘 2
string base = randDigits(rng, len - 1);
printf("%s0\n", base.c_str()); // 末尾 0 ⇒ 含因子 2 和 5
printf("%s0\n", base.c_str());
return 0;
}
printf("%s\n%s\n", randDigits(rng, len).c_str(), randDigits(rng, len).c_str());
return 0;
}
点一下即可编辑
输出
点「运行 ▶」看结果
p2152Count.cpp本页所有数字的出处(两种算法都在进程内跑,带步数计数)
// P2152 的度量程序:./p2152Count csv (本页的数字都出自它)
//
// rounds : ★★★ 换一把**机器无关**的尺子 —— 两种算法各走了多少轮。
// 二进制 gcd 每轮至少把一个数除以 2 ⇒ 轮数 O(log a + log b);
// 而只做减法的版本,最坏要减 a/b 次。
// ratio : ★ 轮数 ÷ 总二进制位数 —— 用来印证「每轮至少减半一次」那条界。
// small : ★ 「用 unsigned long long 读」那一版的触发线:**超过 19 位就装不下**。
// ms : 一万位上两种算法的毫秒(⚠ 进程内计时,不含读入)。
//
// ⚠ 减法版必须有出口:超过 STEP_LIMIT 步就记成「跑不完」(不然这份程序自己会挂住闸门)。
#include <bits/stdc++.h>
#include <chrono>
using namespace std;
using namespace std::chrono;
static const unsigned BASE = 100000000u;
static const int W = 8;
static const long long STEP_LIMIT = 3000000;
struct Big {
vector<unsigned> d;
void trim() { while (d.size() > 1 && d.back() == 0) d.pop_back(); }
bool isZero() const { return d.size() == 1 && d[0] == 0; }
bool isEven() const { return (d[0] & 1u) == 0; }
};
static Big fromString(const string& s) {
Big r;
for (int i = (int)s.size(); i > 0; i -= W) { int l = max(0, i - W); r.d.push_back((unsigned)stoul(s.substr(l, i - l))); }
if (r.d.empty()) r.d.push_back(0);
r.trim(); return r;
}
static string toString(const Big& a) {
string s = to_string(a.d.back());
for (int i = (int)a.d.size() - 2; i >= 0; i--) { string t = to_string(a.d[i]); s += string(W - t.size(), '0') + t; }
return s;
}
static int cmp(const Big& a, const Big& b) {
if (a.d.size() != b.d.size()) return a.d.size() < b.d.size() ? -1 : 1;
for (int i = (int)a.d.size() - 1; i >= 0; i--) if (a.d[i] != b.d[i]) return a.d[i] < b.d[i] ? -1 : 1;
return 0;
}
static void sub(Big& a, const Big& b) {
long long borrow = 0;
for (size_t i = 0; i < a.d.size(); i++) {
long long cur = (long long)a.d[i] - borrow - (i < b.d.size() ? (long long)b.d[i] : 0);
if (cur < 0) { cur += BASE; borrow = 1; } else borrow = 0;
a.d[i] = (unsigned)cur;
}
a.trim();
}
static void halve(Big& a) {
unsigned rem = 0;
for (int i = (int)a.d.size() - 1; i >= 0; i--) {
unsigned long long cur = (unsigned long long)rem * BASE + a.d[i];
a.d[i] = (unsigned)(cur >> 1); rem = (unsigned)(cur & 1ull);
}
a.trim();
}
static void twice(Big& a) {
unsigned carry = 0;
for (size_t i = 0; i < a.d.size(); i++) {
unsigned long long cur = (unsigned long long)a.d[i] * 2 + carry;
a.d[i] = (unsigned)(cur % BASE); carry = (unsigned)(cur / BASE);
}
if (carry) a.d.push_back(carry);
}
/** 二进制 gcd,返回轮数 */
static long long binGcd(Big a, Big b, string& out) {
long long shift = 0, rounds = 0;
while (a.isEven() && b.isEven()) { halve(a); halve(b); shift++; }
while (!a.isZero()) {
while (a.isEven()) halve(a);
while (b.isEven()) halve(b);
if (cmp(a, b) >= 0) sub(a, b); else sub(b, a);
rounds++;
}
while (shift--) twice(b);
out = toString(b);
return rounds;
}
/** 只做减法,返回步数(跑不完给 −1) */
static long long subGcd(Big a, Big b) {
long long steps = 0;
while (!a.isZero() && !b.isZero()) {
int c = cmp(a, b);
if (c == 0) break;
if (c > 0) sub(a, b); else sub(b, a);
if (++steps > STEP_LIMIT) return -1;
}
return steps;
}
static string randDigits(mt19937_64& rng, int len) {
string s; s += char('1' + (int)(rng() % 9ull));
for (int i = 1; i < len; i++) s += char('0' + (int)(rng() % 10ull));
return s;
}
int main(int argc, char** argv) {
bool csv = (argc > 1 && string(argv[1]) == "csv");
mt19937_64 rng(20260901ull);
struct Row { const char* name; int len; bool close; };
Row rows[3] = { {"200 位随机", 200, false}, {"一万位随机", 10000, false}, {"★ 一万位、两数只差 1", 10000, true} };
long long binR[3], subR[3];
double binMs[3], subMs[3];
string ans[3];
for (int i = 0; i < 3; i++) {
string sa = randDigits(rng, rows[i].len), sb;
if (rows[i].close) {
sb = sa;
int j = (int)sb.size() - 1;
while (j >= 0 && sb[j] == '0') { sb[j] = '9'; j--; }
if (j >= 0 && sb[j] > '1') sb[j]--; else if (j >= 0) sb[j] = '1';
} else sb = randDigits(rng, rows[i].len);
Big a = fromString(sa), b = fromString(sb);
auto t0 = steady_clock::now();
binR[i] = binGcd(a, b, ans[i]);
binMs[i] = duration<double, milli>(steady_clock::now() - t0).count();
t0 = steady_clock::now();
subR[i] = subGcd(a, b);
subMs[i] = duration<double, milli>(steady_clock::now() - t0).count();
}
/* 「每轮至少减半一次」那条界:轮数 ÷ 总二进制位数 */
double bits2 = 10000 * 3.3219; // 一万位十进制 ≈ 33219 个二进制位
double ratio = binR[1] / bits2;
/* p2152Small 的触发线:unsigned long long 最多装 19 位十进制 */
int smallLine = 20; // 20 位就可能超 1.8×10¹⁹
/* ★★★ 200 位那一档:为什么「装不下」那一版只被抓不到三成 —— 数一数就知道 */
// 随机的 200 位数**往往互质**(正解答 1),而截断到 2⁶⁴ 之后的两个数**也往往互质**
// ⇒ 两版一起输出 1 ⇒ [「一致有两种:都算对了,和都没算」](/sol/p1746/) 的又一次。
int trueOne = 0, truncOne = 0, differ = 0;
{
// ⚠ **复刻 p2152Gen.cpp 档 1 的种子公式**(否则量出来的数和对拍表对不上 —— 踩过一次)
for (int seed = 1; seed <= 60; seed++) {
mt19937_64 r2(seed * 1000003ull + 20260901ull);
string sa = randDigits(r2, 200), sb = randDigits(r2, 200);
Big a = fromString(sa), b = fromString(sb);
string g;
binGcd(a, b, g);
if (g == "1") trueOne++;
// 截断:只保留末 19 位(unsigned long long 大致能装的范围)
unsigned long long ta = 0, tb = 0;
for (char c : sa) ta = ta * 10 + (unsigned)(c - '0');
for (char c : sb) tb = tb * 10 + (unsigned)(c - '0');
unsigned long long x = ta, y = tb;
while (y) { unsigned long long t = x % y; x = y; y = t; }
if (x == 1) truncOne++;
if (to_string(x) != g) differ++;
}
}
if (csv) {
for (int i = 0; i < 3; i++)
printf("binR%d,%lld\nsubR%d,%lld\nbinMs%d,%.0f\nsubMs%d,%.0f\n", i, binR[i], i, subR[i], i, binMs[i], i, subMs[i]);
printf("ratio,%.2f\nsmallLine,%d\n", ratio, smallLine);
printf("trueOne,%d\ntruncOne,%d\ndiffer,%d\n", trueOne, truncOne, differ);
printf("subTooSlow,%d\n", subR[2] < 0 ? 1 : 0);
return 0;
}
printf("换一把机器无关的尺子:两种算法各走了多少轮(⚠ 减法版超过 %lld 步就记成「跑不完」)\n\n", STEP_LIMIT);
printf(" %-24s %14s %14s %10s %10s\n", "数据", "二进制 gcd 轮数", "只做减法 步数", "毫秒", "毫秒");
for (int i = 0; i < 3; i++) {
char buf[32];
if (subR[i] < 0) snprintf(buf, sizeof(buf), "跑不完");
else snprintf(buf, sizeof(buf), "%lld", subR[i]);
printf(" %-24s %14lld %14s %10.0f %10.0f\n", rows[i].name, binR[i], buf, binMs[i], subMs[i]);
}
printf("\n ★ 一万位 ≈ %.0f 个二进制位,而二进制 gcd 只走了 %lld 轮 ⇒ 轮数 / 位数 = **%.2f**\n",
bits2, binR[1], ratio);
printf(" ⇒ 「每轮至少把一个数除以 2」那条界,在这儿是实打实的。\n");
printf("\n ★ 而「用 unsigned long long 读」那一版的线:**超过 19 位就装不下**(上限 1.8×10¹⁹)。\n");
printf(" ⚠⚠ 可它在 200 位那一档只被抓 **%d / 60** —— 因为随机的 200 位数**往往互质**:\n", differ);
printf(" 正解答 1 的有 %d 轮,而截断之后 gcd 也是 1 的有 %d 轮 ⇒ **两版一起输出 1**。\n", trueOne, truncOne);
return 0;
}
点一下即可编辑
输出
点「运行 ▶」看结果
★ 一页纸
★ 白给的分 题面 20% 档 a, b ≤ 10¹⁸unsigned long long + 循环版 = 20 分
那条线 unsigned long long 最多装 19 位十进制,第 20 位起就废
★★★ 关键一步 把 mod 换成「减法 + 除以 2」 —— 高精度下取模是长除法,太贵
为什么能换 两个奇数的差必是偶数 ⇒ 每轮至少有一个数减半 ⇒ O(log a)
实测那条界 一万位 ≈ 33 219 个二进制位,实际只走 23 471 轮(比值 0.71
⚠ 对拍抓不到什么 「不提因子 2」答案永远对 ⇒ 只能数步数;⚠ 而顶格随机只慢 18 倍,两数相近才是它的死法
高精度要写哪几样 比较、减法、除以 2、乘以 2 —— 没有乘法也没有除法,压位基数 10⁸