题单 · 习题解析

洛谷 P1226 【模板】快速幂

★★★ 第 42 章列的六个错法代进这道题的范围,三个结构上发生不了 —— 而「long long 够不够」的余量正好是 √2

⚠ 先自己写一遍,再往下看

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

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

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

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

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

题目描述

给你三个整数 abp,求 a^b mod p

输入格式

输入只有一行三个整数,分别代表 abp

输出格式

输出一行一个字符串 a^b mod p=s,其中 abp 分别为题目给定的值,s 为运算结果。

说明 / 提示

样例解释2¹⁰ = 10241024 mod 9 = 7

数据规模与约定

对于 100% 的数据,保证 0 ≤ a, b < 2³¹a + b > 02 ≤ p < 2³¹

输入输出样例

输入

2 10 9

输出

2^10 mod 9=7

1这一页要做的事:拿本书的清单,去查这道题的范围

★★★ 第 42 章列了六个错法,这一页问「有几个在这道题上根本发生不了」

快速幂怎么写,第 42 章从头讲到尾了; 那一章第 ⑧ 步还给了一张六个错法各靠什么现形的清单。

这一页只做一件事:把那六条,逐个代进 P1226 的数据范围。

结论是:六条里有三条在这道题上是结构性地发生不了的 —— 而每一条「发生不了」的理由,都是一句能写下来的算术。 ⇒ 一句听起来有道理的提醒,是不是这道题的命门,只能算,不能感觉。

★ 还有一条最要命的和算法一点关系都没有:输出格式(第 ② 步)。

p1226.cpp★ 这一版就能 AC
// 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;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果

顶格数据(a = b = p = 2³¹ - 1 附近)本机实测:0.00 秒(循环只跑 31 次)。

2⚠ 先说那个和算法无关、却一定会挂人的

p1226Fmt.cpp⚠ 算得全对,只输出了一个数
// 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;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果

第 42 章的模板 fast.cpp 输出的是一个数 —— 因为那一章讲的是算法。 而这道题要的是:

a^b mod p=s

mod 两边各一个空格,等号两边没有空格,一个字符都不能差。

⚠ 四档 300 轮,它被抓 300 / 300 —— 而这需要两个前提

参照物也照着格式输出(拿第 42 章那份当参照物的话,两边一起错,对拍全绿); ② 比对不许 strip

第 10 章 P1271 那条踩过的坑: 自己搭的临时对拍最容易在「怎么比」上放水。 正式断言一律逐字节比。

3第 ① 版:老老实实连乘 b 次

p1226Mul.cpp第 ① 版:连乘 O(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;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果

它是对的 —— level 3b ≤ 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 章那六条,逐个代进这道题的范围

p1226Six.cpp六个错法 × 三档数据
// ★★★ 把第 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 ≤ p1 % p 恒等于 1,那一句改不改结果一样。 ⚠ 而第 42 章那一章说得很准:它要「p = 1b = 0 同时成立」—— 这道题把 p = 1 直接划掉了。
  • ★★★ wrongOverflow 最值得算:64 位乘法装得下的最大 p3 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 会掉精度」, 这道题查的是本书自己写的六条。)

★★★ 而最后一行是另一回事 —— 「0 次」有两个完全不同的原因

wrongZero顺手随机那一档也是 0 / 300,看起来和上面三条一样。但它不一样。

第 42 章说它要「a ≡ 0b = 0」。题面确实把 a = 0b = 0 划掉了(a + b > 0)—— 可 a ≡ 0 (mod p) 不等于 a = 0a = p2p3p 都行,而且它们都大于 0。

⇒ 造一档仍然在题面之内、但把两个边界配在一起的数据(b = 0a = k·p), 它当场变成 ★ 300 / 300

★★★ 这给了一个可以照做的判据 —— 对拍抓到 0 次时,造两档:

造哪一档 它活了说明
放宽题面的约束 题面结构上挡死了它 ⇒ 这条提醒对这道题用不上
仍在题面内,但把边界配到一起 题面允许它 ⇒ 只是你的生成器造不到,必须补这一档

第 5 章 P1042 说过「抓不到分两种:概率低 vs 结构上不可能」; 这一页给的是怎么把这两种分开

5★ 这道题真正的溢出线:32 位

p1226Int.cpp⚠ res / a / p 都用 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;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果

p2³¹ - 1res * 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生成器:四档,第 ② 档是这一页的主角

p1226Gen.cpp生成器:四档
// 数据生成器(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;
}
点一下即可编辑
输出
点「运行 ▶」看结果
p1226Zero.cpp⚠ 「底数 ≡ 0 就返回 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;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果
造什么 干什么用的
level 0 照题面顺手随机 基线 —— 抓 Fmt 300、Int 262、Zero 0
★ level 1 b = 0a = k·p 唯一能抓到 Zero 的一档:300 / 300
level 2 pb 都贴着 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
这一页记住三句话
  1. ★★★ 一句听起来有道理的提醒,是不是这道题的命门,只能算,不能感觉。 第 42 章列的六个错法,代进这道题的范围之后三个是结构性地发生不了的a ≥ 0 挡死负数、p ≥ 2 挡死 1 % p、 而 p < 2³¹long long 刚好够 —— 那条线是 3 037 000 500,余量正好是 √2
  2. ★★★ 对拍抓到 0 次的时候,造两档就能知道原因是哪一种。 放宽题面才活 ⇒ 题面挡死了它(这条提醒用不上); 仍在题面内、把边界配到一起就活 ⇒ 题面允许它(你的生成器缺一档)。 wrongZero 就是后者:顺手随机 0/300,配上 b = 0a = k·p 立刻 300/300
  3. ★★ 和算法无关的那一条,才是这道题最先挂人的:输出 a^b mod p=s,不是一个数。 而它对拍抓得到 —— 前提是参照物也照格式输出、比对不许 strip