1 条题解

  • 0
    @ 2026-10-26 10:45:29

    思路与算法

    把两个十进制整数的数位倒序,分别作为多项式A和B的系数。乘积多项式系数c[k]表示所有下标之和为k的数位乘积之和。先精确求卷积,再从低位向高位做十进制进位,最后反向输出,即可避免逐位竖式乘法的平方级复杂度。

    这里使用模数998244353、原根3的数论变换NTT。取变换长度L为不小于lenA+lenB-1的最小二次幂,补零后正变换、逐点相乘、逆变换。两数各最多199999位,每个原始数位至多9,因此每个真实卷积系数不超过81*199999=16199919,严格小于模数。单模结果就是未取模的真实系数,无需CRT,也不存在浮点舍入误差。最大变换长度524288,能整除模数减一。

    进位从0开始,当前位置value=c[k]+carry,输出数位value%10,后续carry=value/10。处理完系数后继续输出剩余进位,去掉最高多余零,反向即为普通十进制表示。参考程序也规范化前导零。

    正确性说明

    十进制展开的乘法分配律给出c[k]=sum(a[i]*b[j]),其中i+j=k,所以先卷积再进位与整数乘法等价。补零长度至少为lenA+lenB-1,循环卷积不会发生首尾混叠。

    模数经逐个试除至平方根验证为质数,模数减一为2^23717,原根3对不同素因子2、7、17的幂检查均不为1,因此所用二次幂长度存在本原单位根。NTT正逆变换利用单位根的正交关系互逆,逐点相乘对应模域卷积。每个真实系数非负且严格小于模数,因此逆变换给出的剩余类唯一对应真实整数系数。

    进位循环保持已经输出的低位,加上尚未处理的系数和进位,表示原乘积不变。每次除以10把该位分离,直到全部系数和进位消耗完,就得到精确乘积。删除高位多余零只改变表示、不改变数值,故最终输出正确。

    复杂度

    令两数数位总长为D,变换长度L是覆盖D-1的二次幂,故L=O(D)。构造并补零两个数组O(L),位逆序置换O(L),三个变换各O(L log L),逐点相乘O(L),进位、去零与输出O(D)。模幂求各层单位根至多O(log L*log p),p固定,包含在上述总界内。总时间O(L log L),额外空间O(L),包括两个变换数组、输入及结果字符串。初始化数组的O(L)开销已计入。

    最大L=524288;两个int数组共4MiB左右,原题128MiB内实际验证。蝶形加法小于2p且小于有符号32位上界;乘法以long long完成,p²小于有符号64位上界。最终标程须按原1秒限制验证,不能缩短最大位数冒充完整范围。

    易错点

    1. 本题是最多199999位,不能套用百位或2000位版本的竖式乘法数据。
    2. 卷积系数还不是十进制数位,必须逐位进位并继续处理最后的carry。
    3. 变换长度覆盖lenA+lenB-1,不能少一个有效高位;逆变换须乘L的模逆。
    4. 单模可行依赖系数上界严格小于模数。本范围已证明,不可无条件推广到更大进制或更大长度。
    5. 处理前导零后仍须保留至少一个字符。输入声明为正整数,测试不加入非法零值。
    6. 覆盖最大长度全9长进位、双随机最大长度、不同长度、二次幂边界和长前导零。独立答案来自Python任意精度整数,最大全9还以闭式公式再次验证,数据非官方原版。

    参考代码(C++11)

    #include <algorithm>
    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    const int MOD = 998244353, ROOT = 3;
    int power(int a, int n) {
        int r = 1;
        while (n) {
            if (n & 1)
                r = (long long)r * a % MOD;
            a = (long long)a * a % MOD;
            n >>= 1;
        }
        return r;
    }
    void ntt(vector<int> &a, bool invert) {
        int n = a.size();
        for (int i = 1, j = 0; i < n; ++i) {
            int bit = n >> 1;
            while (j & bit) {
                j ^= bit;
                bit >>= 1;
            }
            j ^= bit;
            if (i < j)
                swap(a[i], a[j]);
        }
        for (int len = 2; len <= n; len <<= 1) {
            int root = power(ROOT, (MOD - 1) / len);
            if (invert)
                root = power(root, MOD - 2);
            for (int i = 0; i < n; i += len) {
                int w = 1;
                for (int j = 0; j < len / 2; ++j) {
                    int u = a[i + j], v = (long long)a[i + j + len / 2] * w % MOD;
                    int sum = u + v;
                    if (sum >= MOD)
                        sum -= MOD;
                    int diff = u - v;
                    if (diff < 0)
                        diff += MOD;
                    a[i + j] = sum;
                    a[i + j + len / 2] = diff;
                    w = (long long)w * root % MOD;
                }
            }
        }
        if (invert) {
            int inv = power(n, MOD - 2);
            for (int &x : a)
                x = (long long)x * inv % MOD;
        }
    }
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        string s, t;
        cin >> s >> t;
        s.erase(0, min(s.find_first_not_of('0'), s.size() - 1));
        t.erase(0, min(t.find_first_not_of('0'), t.size() - 1));
        if (s == "0" || t == "0") {
            cout << "0\n";
            return 0;
        }
        int size = 1;
        while (size < (int)(s.size() + t.size() - 1))
            size <<= 1;
        vector<int> a(size), b(size);
        for (int i = 0; i < (int)s.size(); ++i)
            a[i] = s[s.size() - 1 - i] - '0';
        for (int i = 0; i < (int)t.size(); ++i)
            b[i] = t[t.size() - 1 - i] - '0';
        ntt(a, false);
        ntt(b, false);
        for (int i = 0; i < size; ++i)
            a[i] = (long long)a[i] * b[i] % MOD;
        ntt(a, true);
        string out;
        long long carry = 0;
        int count = s.size() + t.size() - 1;
        for (int i = 0; i < count || carry; ++i) {
            long long value = carry + (i < count ? a[i] : 0);
            out.push_back('0' + value % 10);
            carry = value / 10;
        }
        while (out.size() > 1 && out.back() == '0')
            out.pop_back();
        reverse(out.begin(), out.end());
        cout << out << '\n';
    }
    
    • 1

    高精度乘法(II)(最多199999位)

    信息

    ID
    1703
    时间
    1000ms
    内存
    128MiB
    难度
    提高+
    标签
    递交数
    1
    已通过
    1
    上传者