0题目原文(本地存了一份)
原题在洛谷上(页头有链接)。别人的网站不归我们管,打不开、改版、题号调整都可能发生。 所以每个解析页都把题面转录一份存在本地,跟着仓库一起进版本库。
转录自洛谷 P1226,日期见页头。两边不一致时信原站。
题目描述
给你三个整数 a、b、p,求 a^b mod p。
输入格式
输入只有一行三个整数,分别代表 a、b、p。
输出格式
输出一行一个字符串 a^b mod p=s,其中 a、b、p 分别为题目给定的值,s 为运算结果。
说明 / 提示
样例解释:2¹⁰ = 1024,1024 mod 9 = 7。
数据规模与约定
对于 100% 的数据,保证 0 ≤ a, b < 2³¹,a + b > 0,2 ≤ p < 2³¹。
输入输出样例
输入
2 10 9
输出
2^10 mod 9=7
1这一页要做的事:拿本书的清单,去查这道题的范围
快速幂怎么写,第 42 章从头讲到尾了; 那一章第 ⑧ 步还给了一张六个错法各靠什么现形的清单。
这一页只做一件事:把那六条,逐个代进 P1226 的数据范围。
结论是:六条里有三条在这道题上是结构性地发生不了的 —— 而每一条「发生不了」的理由,都是一句能写下来的算术。 ⇒ 一句听起来有道理的提醒,是不是这道题的命门,只能算,不能感觉。
★ 还有一条最要命的和算法一点关系都没有:输出格式(第 ② 步)。
// P1226 【模板】快速幂 —— 能 AC 的那一版//// 算法本身第 42 章从头讲到尾了,这一页只做一件事:// **把第 42 章列的六个错法,逐个代进这道题的数据范围,看哪些还能发生。**//// 题面的范围:0 <= a, b < 2³¹,a + b > 0,2 <= p < 2³¹。// 三处直接决定了写法:// ① **a >= 0** ⇒ 第 42 章那句「负数要归一化」在这道题上一次都用不上;// ② **p >= 2** ⇒ `1 % p` 恒等于 1,那句「初值不是 1 是 1 % p」也用不上;// ③ ★ **p < 2³¹** ⇒ `(p-1)² < 2⁶²` ⇒ **long long 刚好够,不用 __int128**。// 余量正好是 √2:装得下的上限是 2³¹·⁵ ≈ 3.04×10⁹,而题面给到 2³¹ ≈ 2.15×10⁹。//// ⚠ 而这道题真正会挂人的一条和算法无关:**输出格式**是 `a^b mod p=s`,不是一个数。
#include <bits/stdc++.h>using namespace std;typedef long long ll;
int main() { ll a, b, p; if (scanf("%lld %lld %lld", &a, &b, &p) != 3) return 0;
ll a0 = a, b0 = b; // 输出里要原样打出来,先存一份 ll res = 1 % p; // p >= 2 ⇒ 这里就是 1;写 1 % p 只是习惯 a %= p; while (b) { if (b & 1) res = res * a % p; // (p-1)² < 2⁶²,long long 装得下 a = a * a % p; b >>= 1; }
printf("%lld^%lld mod %lld=%lld\n", a0, b0, p, res); return 0;}点「运行 ▶」看结果
顶格数据(a = b = p = 2³¹ - 1 附近)本机实测:0.00 秒(循环只跑 31 次)。
2⚠ 先说那个和算法无关、却一定会挂人的
// P1226 ⚠ 错法零:算得完全对,只输出了一个数//// 这是把第 42 章的模板原样搬过来最容易出的事 ——// 那一章的 fast.cpp 输出的就是一个数,因为那一章讲的是算法。//// 而这道题要的是:// a^b mod p=s// 一个字符都不能差(`mod` 两边各一个空格、等号两边**没有**空格)。//// ⇒ 交上去 0 分,而你在本地怎么看都觉得答案是对的。// ★ 这类错对拍抓得到 —— 但前提是**参照物也照着格式输出**,而且比对**不许 strip**。
#include <bits/stdc++.h>using namespace std;typedef long long ll;
int main() { ll a, b, p; if (scanf("%lld %lld %lld", &a, &b, &p) != 3) return 0; ll res = 1 % p; a %= p; while (b) { if (b & 1) res = res * a % p; a = a * a % p; b >>= 1; } printf("%lld\n", res); // ⚠ 就是这里 return 0;}点「运行 ▶」看结果
第 42 章的模板 fast.cpp 输出的是一个数 —— 因为那一章讲的是算法。
而这道题要的是:
a^b mod p=s
mod 两边各一个空格,等号两边没有空格,一个字符都不能差。
① 参照物也照着格式输出(拿第 42 章那份当参照物的话,两边一起错,对拍全绿);
② 比对不许 strip。
⇒ 第 10 章 P1271 那条踩过的坑: 自己搭的临时对拍最容易在「怎么比」上放水。 正式断言一律逐字节比。
3第 ① 版:老老实实连乘 b 次
// P1226 第 ① 版:老老实实连乘 b 次//// 它是对的,只是 b 能到 2³¹-1 ≈ 2.15×10⁹ ——// 一次乘法 + 一次取模按 1.5 纳秒算,光这一步就是 **3 秒以上**,时限 1 秒。//// ⚠ 而它在样例上完全正确(b = 10),本地随手试也看不出任何问题。// 这就是这道题的第一道坎:**样例的 b 和顶格的 b 差了两亿倍。**//// 为了让它在页面上点得动,这一版带一个上限:b 超过 3×10⁸ 就直接罢工报一行字。
#include <bits/stdc++.h>using namespace std;typedef long long ll;
int main() { ll a, b, p; if (scanf("%lld %lld %lld", &a, &b, &p) != 3) return 0; ll a0 = a, b0 = b; if (b > 300000000LL) { printf("b = %lld,连乘要跑 %lld 次 —— 这一版就不跑了,见页面上的外推。\n", b, b); return 0; } ll res = 1 % p; a %= p; for (ll i = 0; i < b; i++) res = res * a % p; printf("%lld^%lld mod %lld=%lld\n", a0, b0, p, res); return 0;}点「运行 ▶」看结果
它是对的 —— level 3(b ≤ 30)那一档 300 轮,它和快速幂逐字节相同。
它唯一的毛病是 b 能到 2³¹ - 1:
| 循环次数 | 本机实测 | |
|---|---|---|
连乘(b = 3 × 10⁸) |
300 000 000 | 1.57 秒 |
连乘(b = 2³¹ - 1,外推) |
2 147 483 647 | ★ 约 11.2 秒 |
| 快速幂(同一组) | ★ 31 | 0.00 秒 |
次数差 6.9 × 10⁷ 倍,时限 1 秒。
样例的 b = 10。样例的 b 和顶格的 b 差了两亿倍 ——
这道题的第一道坎不在算法上,在「有没有把范围代进去算一下」。
4★★★ 把第 42 章那六条,逐个代进这道题的范围
// ★★★ 把第 42 章列的**六个错法**,逐个代进 P1226 的数据范围//// 用法:./p1226Six [轮数] 人话版(默认 300 轮)// ./p1226Six [轮数] csv 给 check:viz 用//// 第 42 章第 ⑧ 步给了一张「靠什么现形」的清单。这一页想问的是另一个问题://// > **那六个错法,在 P1226 这道题的数据范围里,有几个是根本发生不了的?**//// 题面:`0 <= a, b < 2³¹`,`a + b > 0`,`2 <= p < 2³¹`。三处硬约束:// ① `a >= 0` ⇒ 「负数要归一化」那条**永远用不上**;// ② `p >= 2` ⇒ `1 % p` 恒等于 1,「初值不是 1」那条**永远用不上**;// ③ `p < 2³¹` ⇒ `(p-1)² < 2⁶²`,**long long 刚好够** ——// 装得下的上限是 `2³¹·⁵ ≈ 3.04×10⁹`,题面给到 `2³¹ ≈ 2.15×10⁹`,// ★ **余量正好是 √2**(一个是 2 的 31 次方,一个是 31.5 次方)。//// 所以这份程序跑**三档**,同一批错法 —— 三档是为了把「0 次」的两种原因分开:// 档 A **照题面顺手随机**;// 档 A' **仍然照题面**,但专门把两个边界**配在一起**(b = 0 且 a ≡ 0 mod p);// 档 B **把题面那三处各放宽一点**(允许 a < 0 / p = 1 / p 到 2³²)。// ⇒ 在 A 是 0、在 A' 活了 ⇒ 题面**允许**,只是顺手生成器造不到;// 在 A 和 A' 都是 0、只有 B 活 ⇒ 题面**结构上挡死了**它。//// ⚠ 演示溢出的那一版用的是 unsigned 乘法(回绕是标准规定的),// 真写 `long long` 相乘溢出是 UB,-O2 下不可复现 —— 那样这一页的数就钉不住了。
#include <bits/stdc++.h>using namespace std;typedef long long ll;typedef __int128 lll;
/* 参照物:处处正确(__int128 乘法、负数归一化、1 % p、0⁰ = 1) */static ll okFull(ll a, ll b, ll p) { ll res = 1 % p; a %= p; if (a < 0) a += p; while (b) { if (b & 1) res = (ll)((lll)res * a % p); a = (ll)((lll)a * a % p); b >>= 1; } return res;}static ll wrongLoop(ll a, ll b, ll p) { // while (b > 1):最后一位没乘进去 ll res = 1 % p; a %= p; if (a < 0) a += p; while (b > 1) { if (b & 1) res = (ll)((lll)res * a % p); a = (ll)((lll)a * a % p); b >>= 1; } return res;}static ll wrongOrder(ll a, ll b, ll p) { // 先平方、再判这一位 —— 整整错一位 ll res = 1 % p; a %= p; if (a < 0) a += p; while (b) { a = (ll)((lll)a * a % p); if (b & 1) res = (ll)((lll)res * a % p); b >>= 1; } return res;}static ll wrongNeg(ll a, ll b, ll p) { // 不把负数归一化 ll res = 1 % p; a %= p; // ⚠ 少了 if (a < 0) a += p; while (b) { if (b & 1) res = (ll)((lll)res * a % p); a = (ll)((lll)a * a % p); b >>= 1; } return res;}/** 64 位乘法:`x * y` 超过 2⁶³ 就회绕成负数 —— 用 unsigned 算再当成有符号看,结果可复现。 */static ll mul64(ll x, ll y, ll p) { ll t = (ll)((unsigned long long)x * (unsigned long long)y); return t % p; // t 变负数时这里就跟着负了}static ll wrongOverflow(ll a, ll b, ll p) { // 乘法只用 64 位(第 42 章那一章要 __int128) ll res = 1 % p; a %= p; if (a < 0) a += p; while (b) { if (b & 1) res = mul64(res, a, p); // ★ p > 3.04×10⁹ 时这里会翻车 a = mul64(a, a, p); b >>= 1; } return res;}static ll wrongInt(ll a, ll b, ll p) { // ★ 这道题真正的溢出线:用 32 位 unsigned res = (unsigned)(1 % p), aa = (unsigned)(a % p), pp = (unsigned)p; while (b) { if (b & 1) res = res * aa % pp; aa = aa * aa % pp; b >>= 1; } return (ll)res;}static ll wrongOne(ll a, ll b, ll p) { // 初值写成 1(不是 1 % p) ll res = 1; a %= p; if (a < 0) a += p; while (b) { if (b & 1) res = (ll)((lll)res * a % p); a = (ll)((lll)a * a % p); b >>= 1; } return res;}static ll wrongZero(ll a, ll b, ll p) { // 「底数 ≡ 0 就返回 0」—— 把 0⁰ 也吞了 ll aa = a % p; if (aa < 0) aa += p; if (aa == 0) return 0; return okFull(a, b, p);}
static const char* NAMES[7] = { "wrongLoop", "wrongOrder", "wrongNeg", "wrongOverflow", "wrongOne", "wrongZero", "wrongInt"};typedef ll (*Fn)(ll, ll, ll);static Fn FNS[7] = { wrongLoop, wrongOrder, wrongNeg, wrongOverflow, wrongOne, wrongZero, wrongInt };
int main(int argc, char** argv) { int rounds = (argc > 1) ? atoi(argv[1]) : 300; bool csv = (argc > 2 && string(argv[2]) == "csv");
/* 装得下的 p 的上限:最大的 p 使 (p-1)² < 2⁶³ */ ll ovLimit = 0; { ll lo = 2, hi = 5000000000LL; while (lo < hi) { ll mid = lo + (hi - lo + 1) / 2; if ((lll)(mid - 1) * (mid - 1) < (lll)9223372036854775807LL) lo = mid; else hi = mid - 1; } ovLimit = lo; }
int caught[3][7] = {{0}}; int b0Rounds = 0, azeroRounds = 0; for (int lv = 0; lv < 3; lv++) { mt19937_64 rng(20260827ull + (unsigned)lv); for (int r = 0; r < rounds; r++) { ll a, b, p; if (lv == 0) { // 档 A:照题面顺手随机 p = 2 + (ll)(rng() % 2147483646ull); // [2, 2³¹) b = (ll)(rng() % 2147483648ull); // [0, 2³¹) a = (ll)(rng() % 2147483648ull); if (r % 7 == 0) b = 0; // 顺手也多造些 b = 0 if (a + b == 0) a = 1; if (b == 0) { b0Rounds++; if (a % p == 0) azeroRounds++; } } else if (lv == 1) { // 档 A':仍在题面内,两个边界配在一起 p = 2 + (ll)(rng() % 1000000ull); // p 小一点,好让 a = k·p 也塞得进 2³¹ a = p * (1 + (ll)(rng() % 3)); // ★ a ≡ 0 (mod p),且 a > 0 b = 0; // ★ 同时 b = 0 } else { // 档 B:三处各放宽一点 p = 1 + (ll)(rng() % 4294967295ull); // [1, 2³²) b = (ll)(rng() % 2147483648ull); a = (ll)(rng() % 4294967296ull) - 2147483648LL;// 允许负 if (r % 5 == 0) p = 1; if (r % 7 == 0) b = 0; if (a + b == 0) a = 1; } ll want = okFull(a, b, p); for (int i = 0; i < 7; i++) if (FNS[i](a, b, p) != want) caught[lv][i]++; } }
if (csv) { printf("rounds,%d\novLimit,%lld\nb0Rounds,%d\nazeroRounds,%d\n", rounds, ovLimit, b0Rounds, azeroRounds); for (int i = 0; i < 7; i++) printf("A_%s,%d\n", NAMES[i], caught[0][i]); for (int i = 0; i < 7; i++) printf("C_%s,%d\n", NAMES[i], caught[1][i]); for (int i = 0; i < 7; i++) printf("B_%s,%d\n", NAMES[i], caught[2][i]); return 0; }
printf("同一批错法(第 42 章第 ⑧ 步那张清单),三档数据各 %d 轮:\n\n", rounds); printf(" %-16s %12s %12s %12s %s\n", "错法", "A 顺手随机", "A' 配边界", "B 放宽题面", "结论"); printf(" %-16s %12s %12s %12s\n", "----------------", "------------", "------------", "------------"); for (int i = 0; i < 7; i++) { const char* verdict = ""; if (caught[0][i] == 0 && caught[1][i] == 0 && caught[2][i] > 0) verdict = "★ 题面结构上挡死了它"; else if (caught[0][i] == 0 && caught[1][i] > 0) verdict = "★ 题面允许,只是顺手造不到"; else if (caught[0][i] > 0) verdict = "白送"; printf(" %-16s %9d/%d %9d/%d %9d/%d %s\n", NAMES[i], caught[0][i], rounds, caught[1][i], rounds, caught[2][i], rounds, verdict); } printf("\n 档 A 里 b = 0 的有 %d 轮,其中 a ≡ 0 (mod p) 的有 %d 轮 —— wrongZero 靠的就是后面这个数。\n", b0Rounds, azeroRounds); printf("\n ★ 那条溢出线:64 位乘法装得下的最大 p 是 %lld(≈ %.2f×10⁹),\n", ovLimit, ovLimit / 1e9); printf(" 而题面给到 2³¹ - 1 = 2147483647(≈ 2.15×10⁹)—— 余量 %.3f 倍,正好是 √2。\n", (double)ovLimit / 2147483647.0); printf(" ⇒ **long long 刚好够,不用 __int128** —— 这是算出来的,不是猜的。\n"); return 0;}点「运行 ▶」看结果
三档数据,同一批错法,各 300 轮:
| 第 42 章列的错法 | 那一章说它靠什么现形 | A 顺手随机 | A’ 配边界 | B 放宽题面 | 结论 |
|---|---|---|---|---|---|
wrongLoop while (b > 1) |
b ≥ 1 | 257 | 0 | 206 | 白送 |
wrongOrder 先平方再判位 |
几乎白送 | 257 | 0 | 206 | 白送 |
wrongNeg 不归一化负数 |
a < 0 | 0 | 0 | 55 | ★ 题面挡死 |
wrongOverflow 乘法只用 64 位 |
p > 3.04×10⁹ | 0 | 0 | 51 | ★ 题面挡死 |
wrongOne 初值写成 1 |
p = 1 且 b = 0 | 0 | 0 | 9 | ★ 题面挡死 |
wrongZero 0⁰ 当成 0 |
a ≡ 0 且 b = 0 | 0 | ★ 300 | 0 | ★★ 题面允许,顺手造不到 |
wrongNeg:题面写着0 ≤ a⇒ 负数根本进不来。wrongOne:题面写着2 ≤ p⇒1 % p恒等于1,那一句改不改结果一样。 ⚠ 而第 42 章那一章说得很准:它要「p = 1且b = 0同时成立」—— 这道题把p = 1直接划掉了。- ★★★
wrongOverflow最值得算:64 位乘法装得下的最大p是 3 037 000 500((p-1)² < 2⁶³解出来的),而题面给到2³¹ - 1 = 2 147 483 647。 ⇒ 余量 1.414 倍 —— 正好是 √2(一个是 2 的 31 次方,一个是 31.5 次方)。 所以这道题long long刚好够,不用__int128。
⇒ 同一句提醒,换一道题就能从「命门」变成「用不上」。判据只有一个:把范围代进去算。
(第 10 章 P1068 那条的同一族:那道题查的是「别用 1.5 会掉精度」,
这道题查的是本书自己写的六条。)
wrongZero 在顺手随机那一档也是 0 / 300,看起来和上面三条一样。但它不一样。
第 42 章说它要「a ≡ 0 且 b = 0」。题面确实把 a = 0 且 b = 0 划掉了(a + b > 0)——
可 a ≡ 0 (mod p) 不等于 a = 0:a = p、2p、3p 都行,而且它们都大于 0。
⇒ 造一档仍然在题面之内、但把两个边界配在一起的数据(b = 0 且 a = k·p),
它当场变成 ★ 300 / 300。
★★★ 这给了一个可以照做的判据 —— 对拍抓到 0 次时,造两档:
| 造哪一档 | 它活了说明 |
|---|---|
| 放宽题面的约束 | 题面结构上挡死了它 ⇒ 这条提醒对这道题用不上 |
| 仍在题面内,但把边界配到一起 | 题面允许它 ⇒ 只是你的生成器造不到,必须补这一档 |
⇒ 第 5 章 P1042 说过「抓不到分两种:概率低 vs 结构上不可能」; 这一页给的是怎么把这两种分开。
5★ 这道题真正的溢出线:32 位
// P1226 ⚠ 错法:把 res / a / p 都放进 32 位//// 这是这道题**唯一一个真的会因为溢出而挂**的写法:// p 到 2³¹-1 ⇒ `res * a` 最大 (p-1)² ≈ 4.6×10¹⁸,32 位装 4.29×10⁹ 都够呛。//// ★ 对照第 42 章那条「乘法要用 __int128」:那一章的 p 能到 10¹⁸,所以非 __int128 不可;// 这道题 p < 2³¹ ⇒ (p-1)² < 2⁶² ⇒ **long long 刚好够**(余量 √2,见 p1226Six.cpp)。// ⇒ **同一句提醒,换一道题就从「命门」变成「用不上」。判据只有一个:把范围代进去算。**//// ⚠ 这里写 unsigned 而不是 int:有符号溢出是 UB,-O2 下不可复现,页面上的数就钉不住了。
#include <bits/stdc++.h>using namespace std;typedef long long ll;
int main() { ll a, b, p; if (scanf("%lld %lld %lld", &a, &b, &p) != 3) return 0; ll a0 = a, b0 = b;
unsigned res = (unsigned)(1 % p), aa = (unsigned)(a % p), pp = (unsigned)p; while (b) { if (b & 1) res = res * aa % pp; // ⚠ 32 位相乘,回绕 aa = aa * aa % pp; b >>= 1; } printf("%lld^%lld mod %lld=%u\n", a0, b0, p, res); return 0;}点「运行 ▶」看结果
p 到 2³¹ - 1 ⇒ res * a 最大 (p-1)² ≈ 4.6 × 10¹⁸,32 位连 4.29 × 10⁹ 都装不下。
⚠ 而它样例照过(p = 9,一切都很小)。四档 300 轮:
| 生成器 | 被抓 |
|---|---|
| level 0 顺手随机 | 262 / 300 |
level 1 配边界(b = 0) |
0 / 300 ← b = 0 时一次乘法都不做 |
| level 2 顶格 | ★ 300 / 300 |
level 3 b ≤ 30 |
289 / 300 |
把这道题和第 42 章并排看:
| 第 42 章 | P1226 | |
|---|---|---|
p 的上限 |
10¹⁸ |
2³¹ - 1 |
| 32 位够吗 | ✗ | ✗ 262/300 被抓 |
| 64 位够吗 | ✗ 要 __int128 |
★ 够,余量 √2 |
⇒ 「乘法要用 __int128」这句话没有主语就是错的 ——
主语是 p 的上限,而那条线是 3 037 000 500。
6生成器:四档,第 ② 档是这一页的主角
// 数据生成器(P1226 对拍用):`./p1226Gen <seed> [level]`//// level 0(默认)★ **照题面顺手随机**:0 <= a,b < 2³¹,2 <= p < 2³¹// level 1 ★★ **在题面内把两个边界配在一起**:b = 0 **且** a 是 p 的倍数// —— 专门给 `wrongZero` 用的。题面允许,顺手随机造不出来。// level 2 顶格:p 和 b 都贴着 2³¹-1(量的是速度,不是正确性)// level 3 b 很小(<= 30)—— 这一档连乘版跑得动,用来验「连乘 ≡ 快速幂」//// ⚠ 题面保证 a + b > 0,生成器必须照办(a = b = 0 那一组不合法,答案也没定义)。
#include <bits/stdc++.h>using namespace std;static mt19937_64 rng;static long long ri(long long lo, long long hi) { return lo + (long long)(rng() % (unsigned long long)(hi - lo + 1));}
int main(int argc, char** argv) { rng.seed((argc > 1) ? (unsigned long long)atoll(argv[1]) : 1ull); int level = (argc > 2) ? atoi(argv[2]) : 0;
long long a, b, p; if (level == 1) { p = ri(2, 1000000); a = p * ri(1, 3); // ★ a ≡ 0 (mod p),而且 a > 0 b = 0; // ★ 同时 b = 0 } else if (level == 2) { p = ri(2147000000LL, 2147483647LL); b = ri(2147000000LL, 2147483647LL); a = ri(2147000000LL, 2147483647LL); } else if (level == 3) { p = ri(2, 2147483647LL); b = ri(0, 30); a = ri(0, 2147483647LL); if (a + b == 0) a = 1; } else { p = ri(2, 2147483647LL); b = ri(0, 2147483647LL); a = ri(0, 2147483647LL); if (rng() % 7 == 0) b = 0; if (a + b == 0) a = 1; } printf("%lld %lld %lld\n", a, b, p); return 0;}点「运行 ▶」看结果
// P1226 ⚠ 错法:顺手加了一句「底数是 0 就直接返回 0」//// 这是第 42 章清单里的 `wrongZero`,那一章说它要「**a ≡ 0 且 b = 0** 同时成立」才现形。//// ★ 这道题的题面把 `a + b > 0` 写死了 —— 于是 a = 0 且 b = 0 确实不可能。// 但**「a ≡ 0 (mod p)」不等于「a = 0」**:a = p、2p、3p… 都行,而且它们都 > 0。// ⇒ 这个错法在这道题上**是能发生的**,只是顺手写的生成器造不出来// (要 b = 0 和 a 是 p 的倍数**同时**成立)。//// ⚠ 它和 wrongNeg / wrongOne / wrongOverflow 那三个不是一回事 ——// 那三个是**题面结构上挡死的**,放宽题面才能活。// 分辨方法见 p1226Six.cpp 那张三档表。
#include <bits/stdc++.h>using namespace std;typedef long long ll;
int main() { ll a, b, p; if (scanf("%lld %lld %lld", &a, &b, &p) != 3) return 0; ll a0 = a, b0 = b;
ll res; if (a % p == 0) { res = 0; // ⚠ 就是这一句:0⁰ 也被它吞了 } else { res = 1 % p; a %= p; while (b) { if (b & 1) res = res * a % p; a = a * a % p; b >>= 1; } } printf("%lld^%lld mod %lld=%lld\n", a0, b0, p, res); return 0;}点「运行 ▶」看结果
| 档 | 造什么 | 干什么用的 |
|---|---|---|
| level 0 | 照题面顺手随机 | 基线 —— 抓 Fmt 300、Int 262、Zero 0 |
| ★ level 1 | b = 0 且 a = k·p |
唯一能抓到 Zero 的一档:300 / 300 |
| level 2 | p、b 都贴着 2³¹-1 |
Int 在这儿是 300 / 300 |
| level 3 | b ≤ 30 |
这一档连乘版跑得动 ⇒ 验「连乘 ≡ 快速幂」0 次不一致 |
7一张总表
| 版本 | 做法 | 样例 | 顺手随机 300 轮 | 配边界 300 轮 | 顶格 | 结果 |
|---|---|---|---|---|---|---|
① p1226Fmt |
只输出一个数 | ✗ | ✗ 300/300 被抓 | ✗ 300/300 | — | ✗ 0 分 |
② p1226Mul |
连乘 O(b) |
✓ | ✓ | ✓ | 外推 11.2 秒 | ✗ TLE |
③ p1226Int |
快速幂,32 位 | ✓ | ✗ 262/300 被抓 | ✓(b=0) |
✗ 300/300 | ✗ WA |
④ p1226Zero |
快速幂 +「a≡0 返回 0」 |
✓ | ★ ✓ 0/300 | ✗ 300/300 | ✓ | ✗ WA |
⑤ p1226 |
快速幂 + long long + 格式 |
✓ | ✓ | ✓ | 0.00 秒 | ★ AC |
- ★★★ 一句听起来有道理的提醒,是不是这道题的命门,只能算,不能感觉。
第 42 章列的六个错法,代进这道题的范围之后三个是结构性地发生不了的:
a ≥ 0挡死负数、p ≥ 2挡死1 % p、 而p < 2³¹让long long刚好够 —— 那条线是 3 037 000 500,余量正好是 √2。 - ★★★ 对拍抓到 0 次的时候,造两档就能知道原因是哪一种。
放宽题面才活 ⇒ 题面挡死了它(这条提醒用不上);
仍在题面内、把边界配到一起就活 ⇒ 题面允许它(你的生成器缺一档)。
wrongZero就是后者:顺手随机0/300,配上b = 0且a = k·p立刻 300/300。 - ★★ 和算法无关的那一条,才是这道题最先挂人的:输出
a^b mod p=s,不是一个数。 而它对拍抓得到 —— 前提是参照物也照格式输出、比对不许strip。