用对数推导 2^P-1 的位数,再用只保留低 500 位的高精度快速幂计算十进制后缀。
OJ: luogu
题目 ID: P1045
难度:普及-
标签:高精度数学快速幂python
日期: 2026-07-15 22:10
目录
题意
给定 0,固定输出 10 行,每行 50 位。
思路
朴素写法为什么可能只有部分分
最直接的方法是用一个 500 位数组保存低位,从 1 开始连续乘
先看这个适合小数据和对拍的朴素实现:
/**
* 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-07-19 08:59
* update_at: 2026-07-19 08:59
*/
// brute.cpp:小数据朴素解,逐次乘 2,用来展示部分分做法并辅助对拍。
#include <bits/stdc++.h>
using namespace std;
const int KEEP_DIGITS = 500;
int p;
int digits[KEEP_DIGITS]; // 低位在前,只保存当前数的最后 500 位
void multiply_by_two() {
int carry = 0;
for (int i = 0; i < KEEP_DIGITS; i++) {
int value = digits[i] * 2 + carry;
digits[i] = value % 10;
carry = value / 10;
}
}
void subtract_one() {
int pos = 0;
while (digits[pos] == 0) {
digits[pos] = 9;
pos++;
}
digits[pos]--;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> p;
int digit_count = (int)(p * log10(2.0)) + 1;
cout << digit_count << '\n';
digits[0] = 1;
for (int i = 1; i <= p; i++) {
multiply_by_two();
}
subtract_one();
for (int i = KEEP_DIGITS - 1; i >= 0; i--) {
cout << digits[i];
if (i % 50 == 0) cout << '\n';
}
return 0;
}一次乘 2 要扫描 500 个数位,总复杂度为
位数为什么可以用对数计算
先从十进制位数的定义出发。若正整数
因为常用对数
这说明
令
所以
题目求的是
digit_count = floor(P * log10(2)) + 1为什么取模就能得到最后 500 位
令
0,所以
模运算与乘法相容:
所以每次高精度乘法后都可以立即舍弃超过 500 位的高位,不会影响最终答案。
C++:高精度二进制快速幂
把
answer_digits初始为 1,base_digits初始为 2。- 若当前指数是奇数,把
base_digits乘进答案。 - 将底数平方,指数除以 2。
- 重复到指数变成 0。
指数每轮至少减半,只需 multiply_mod() 用竖式乘法计算两个 500 位数的乘积,只保留下标小于 500 的部分,这就等价于模
Python:三参数 pow
Python 内置的:
pow(base, exponent, modulus)会直接进行模快速幂,不会先构造完整的
modulus = 10 ** 500
last_digits = (pow(2, P, modulus) - 1) % modulusstr(last_digits).zfill(500) 在左侧补足前导零,再用字符串切片每 50 位输出一行。
正确性说明
- 位数公式由
取常用对数直接得到; 不可能是 10 的整数次幂,所以减 1 不改变位数。 - 快速幂始终保持
。指数为奇数时先把一个底数乘入答案,再平方底数并把指数减半,不变量保持成立;指数变成 0 时,答案就是 的后 500 位。 - 最后执行减 1,得到
;固定补足 500 位后,输出格式与题目要求一致。
常见失分点
- 连续执行
次高精度乘 2,最大数据下运算次数过多。 - 先计算完整的
,没有从“只求最后 500 位”想到取模。 - 忘记在模幂结果上减 1,或者减 1 时没有处理借位。
- 输出了不足 500 位的后缀,没有在高位补
0。 - 没有严格输出 10 行,或每行不是 50 位。
代码
C++17 正式解
/**
* 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-07-19 08:51
* update_at: 2026-07-19 08:51
*/
#include <bits/stdc++.h>
using namespace std;
const int KEEP_DIGITS = 500;
int p;
int answer_digits[KEEP_DIGITS]; // 低位在前,只保存答案的最后 500 位
int base_digits[KEEP_DIGITS];
int temp_digits[KEEP_DIGITS];
// 计算两个 500 位数的乘积,并舍去超过 500 位的高位。
void multiply_mod(const int a[], const int b[], int result[]) {
long long product[KEEP_DIGITS] = {0};
for (int i = 0; i < KEEP_DIGITS; i++) {
if (a[i] == 0) continue;
for (int j = 0; i + j < KEEP_DIGITS; j++) {
product[i + j] += a[i] * b[j];
}
}
for (int i = 0; i < KEEP_DIGITS; i++) {
if (i + 1 < KEEP_DIGITS) {
product[i + 1] += product[i] / 10;
}
result[i] = product[i] % 10;
}
}
void copy_digits(int target[], const int source[]) {
for (int i = 0; i < KEEP_DIGITS; i++) {
target[i] = source[i];
}
}
// 二进制快速幂计算 2^p,整个过程只保留最后 500 位。
void fast_power() {
answer_digits[0] = 1;
base_digits[0] = 2;
int exponent = p;
while (exponent > 0) {
if (exponent % 2 == 1) {
multiply_mod(answer_digits, base_digits, temp_digits);
copy_digits(answer_digits, temp_digits);
}
multiply_mod(base_digits, base_digits, temp_digits);
copy_digits(base_digits, temp_digits);
exponent /= 2;
}
}
void subtract_one() {
int pos = 0;
while (answer_digits[pos] == 0) {
answer_digits[pos] = 9;
pos++;
}
answer_digits[pos]--;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> p;
int digit_count = (int)(p * log10(2.0)) + 1;
cout << digit_count << '\n';
fast_power();
subtract_one();
for (int i = KEEP_DIGITS - 1; i >= 0; i--) {
cout << answer_digits[i];
if (i % 50 == 0) cout << '\n';
}
return 0;
}Python 简洁写法
Python 的三参数 pow 已经实现了模快速幂,因此代码会比手写高精度 C++ 短很多:
from math import log10
KEEP_DIGITS = 500
LINE_WIDTH = 50
p = int(input())
digit_count = int(p * log10(2)) + 1
modulus = 10 ** KEEP_DIGITS
last_digits = (pow(2, p, modulus) - 1) % modulus
text = str(last_digits).zfill(KEEP_DIGITS)
output = [str(digit_count)]
for start in range(0, KEEP_DIGITS, LINE_WIDTH):
output.append(text[start:start + LINE_WIDTH])
print("\n".join(output))复杂度
设保留的十进制位数
- 朴素逐次乘 2:时间复杂度为
,空间复杂度为 。 - C++ 高精度快速幂:每次竖式乘法为
,共进行 次乘法,因此时间复杂度为 ,空间复杂度为 。 - Python 三参数
pow:进行次模乘,参与运算的整数始终限制在模 的范围内;输出额外处理固定 500 个字符。
总结
这道题要把一个巨大整数拆成两个小问题:完整数的位数由对数确定,十进制后缀由模
C++ 用“高精度乘法 + 二进制快速幂”把逐次乘 2 的 pow。最关键的习惯是:题目只问巨大整数的一部分信息时,不要先构造整个巨大整数。