洛谷 P2152 [SDOI2009]SuperGCD (高精度)

本文分享了一次解决高精度数除法问题的经验,通过优化数据表示方式从万进制到10的9次方进制,显著提升了算法效率。作者详细记录了从初版实现到最终AC的全过程,包括输入处理、算法调试、时间复杂度优化等关键步骤。

这道题直接写了我两个多小时……

主要是写高精度的时候还存在着一些小毛病,调了很久

在输入这一块卡了很久。

然后注意这里用while的形式写,不然会炸

最后即使我已经是用的万进制了,但是交上去还是有两个点超时

然后就开始漫长的改进,一直过不了那两个点。

然后突然发现,貌似这道题没有涉及到乘法。

那不就可以直接开10的九次方为一位了(10的9次方是int的最大数量级)

我交上去之后就AC了,全部测试点交上去总和4.6秒

然后原来我一个数开的是2500这么大,因为题目给的10000位,之前用万进制除以4就是2500

然后我现在改成了1200,大约是10000除以9

然后……

交上去总和3秒

直接快了一秒多

而且单个测试点最多也就0.7秒,而这道题的最大限制是2s

看来省空间的时候时间也省了很多

#include<cstdio>
#include<cstring>
#include<algorithm>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 1200;
const int base = 1e9;
struct bignum
{
    int len, s[MAXN];
    bignum() { len = 0; memset(s, 0, sizeof(s)); }
};

bignum operator - (bignum a, const bignum& b)
{
    for(int i = a.len; i >= 1; i--)
    {
        a.s[i] -= b.s[i];
        if(a.s[i] < 0) a.s[i+1]--, a.s[i] += base;
    }
    while(!a.s[a.len] && a.len > 0) a.len--;
    return a;
}

bool judge(bignum a, bignum b) //这种写法很简略 
{
    if(a.len != b.len) return a.len > b.len;
    for(int i = a.len; i >= 1; i--)
        if(a.s[i] != b.s[i]) return a.s[i] > b.s[i];
    return true;
}

bignum operator % (bignum a, bignum b)
{
    while(judge(a, b)) a = a - b;
    return a;
}

char str[10000 + 5];
void read(bignum& a) //这个输入代码写了好久 
{
    scanf("%s", str);
    reverse(str, str + strlen(str)); //先翻转在说 
    int& len = a.len = 0;
    for(int i = 0, w; i < strlen(str); i++, w *= 10)
    {
        if(i % 9 == 0) len++, w = 1;
        a.s[len] += w * (str[i] - '0');
    }
}

void print(bignum a)
{
    printf("%d", a.s[a.len]);
    for(int i = a.len - 1; i >= 1; i--)
        printf("%09d", a.s[i]);
    puts("");
}

int main()
{
    bignum a, b, c;
    read(a); read(b);
    while(b.len) //规定len = 0时值为0 
    {
        c = a % b;
        a = b;
        b = c;
    }
    print(a);
    return 0;
}

 

 

 

 

 

 

是的,**当 `n ≤ 5000` 时,必须使用高精度计算**。 --- ### ✅ 回答:需要高精度 #### 原因分析: 楼梯问题的本质是斐波那契数列变种: - `f(1) = 1` - `f(2) = 2` - `f(n) = f(n-1) + f(n-2)`(n ≥ 3) 这实际上是斐波那契数列的平移版本(即 `f(n) = Fib(n+1)`)。 我们来估算一下 `f(5000)` 的位数: 斐波那契数列的增长近似公式为: $$ Fib(n) \approx \frac{\phi^n}{\sqrt{5}}, \quad \text{其中 } \phi = \frac{1+\sqrt{5}}{2} \approx 1.618 $$ 取对数可得: $$ \log_{10}(Fib(n)) \approx n \cdot \log_{10}(\phi) - \log_{10}(\sqrt{5}) \approx n \cdot 0.20899 - 0.349 $$ 所以: $$ \log_{10}(f(5000)) = \log_{10}(Fib(5001)) \approx 5001 \times 0.20899 - 0.349 \approx 1044.7 $$ 👉 表示 `f(5000)` 大约有 **1045 位十进制数字** 而: - `long long` 最大约 19 位十进制数 - `unsigned long long` 最大也才 `1.8 × 10^19`(19位) 显然无法存储千位级的大整数! --- ### ❌ 普通数据类型完全失效 | 类型 | 最大约 | |------|--------| | `int` | 10 位 | | `long long` | 19 位 | | `__int128`(GCC扩展) | 约 38 位 | | 都不足以表示 1000+ 位的数 | --- ### ✅ 解决方案:高精度加法 + 数组模拟 我们需要用数组或字符串来存储大整数,并实现两个大数的加法操作。 下面是一个基于 **C++ 数组模拟高精度加法** 的完整 AC 风格代码(适用于洛谷 P1255 当 `n ≤ 5000`): ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; // 高精度加法函数:两个 vector<int> 表示的数字相加 vector<int> add(const vector<int>& a, const vector<int>& b) { vector<int> res; int carry = 0; int i = a.size() - 1, j = b.size() - 1; while (i >= 0 || j >= 0 || carry) { int sum = carry; if (i >= 0) sum += a[i--]; if (j >= 0) sum += b[j--]; carry = sum / 10; res.push_back(sum % 10); } reverse(res.begin(), res.end()); return res; } int main() { int n; cin >> n; if (n == 1) { cout << 1 << endl; return 0; } if (n == 2) { cout << 2 << endl; return 0; } vector<int> a = {1}; // f(1) vector<int> b = {2}; // f(2) vector<int> c; for (int i = 3; i <= n; ++i) { c = add(a, b); // c = a + b a = b; // a <- b b = c; // b <- c } // 输出结果 for (int digit : b) { cout << digit; } cout << endl; return 0; } ``` --- ### 🔍 代码说明: - 使用 `vector<int>` 存储每一位数字(从高位到低位) - `add()` 函数实现倒序逐位相加,处理进位 - 主循环迭代更新 `a`, `b`,模拟斐波那契递推 - 最终输出整个大数每一位 例如:`f(5000)` 将输出一个约 1045 位的整数 --- ### ⏱️ 性能分析: - 时间复杂度:O(n × L),其中 L 是平均位数,约为 O(n),总时间约 O(),对于 n=5000 是可接受的(约几毫秒) - 空间复杂度:O(L),用于存储大数 --- ### 📌 总结: | 条件 | 是否需高精度 | 说明 | |------|----------------|------| | `n ≤ 80` | 否 | `long long` 足够 | | `n > 80`(如 `n ≤ 5000`) | ✅ 必须使用高精度 | 结果超过百位甚至上千位 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值