1 条题解

  • 0
    @ 2025-11-18 8:31:08

    「POI2018 R3」多项式 Polynomial 题解

    题目分析

    我们有一个 nn 次多项式:

    $$W(x) = a_0 + a_1x + a_2x^2 + \cdots + a_{n-1}x^{n-1} $$

    需要在 nn 个点 q1,q2,,qnq^1, q^2, \ldots, q^n 处求值,其中:

    • nn22 的幂
    • qn1(modm)q^n \equiv 1 \pmod{m}

    关键性质

    由于 qn1(modm)q^n \equiv 1 \pmod{m},序列 {qimodm}\{q^i \bmod m\} 是周期为 nn 的:

    qi+nqiqnqi(modm)q^{i+n} \equiv q^i \cdot q^n \equiv q^i \pmod{m}

    数学推导

    问题重述

    我们需要计算:

    S=k=1nW(qk)modmS = \sum_{k=1}^n W(q^k) \bmod m

    $$W(q^k) = \sum_{j=0}^{n-1} a_j q^{kj} \bmod m \quad \text{for } k=1,\ldots,n $$

    利用循环性质

    由于 qn1(modm)q^n \equiv 1 \pmod{m},我们可以将指数模 nn

    qkjqkjmodn(modm)q^{kj} \equiv q^{kj \bmod n} \pmod{m}

    定义序列 br=jr(modn)ajb_r = \sum_{j \equiv r \pmod{n}} a_j(实际上 j<nj < n,所以就是 ara_r),那么:

    W(qk)=r=0n1brqkrmodmW(q^k) = \sum_{r=0}^{n-1} b_r q^{kr} \bmod m

    这看起来像是一个离散傅里叶变换(DFT)的形式。

    算法设计

    方法1:直接计算(小数据)

    对于 n210n \leq 2^{10},可以直接计算:

    def direct_method(n, m, q, a):
        # 预处理 q 的幂次
        q_pow = [1] * (n + 1)
        for i in range(1, n + 1):
            q_pow[i] = (q_pow[i-1] * q) % m
        
        # 计算每个点的值
        values = []
        total = 0
        for k in range(1, n + 1):
            val = 0
            for j in range(n):
                val = (val + a[j] * pow(q_pow[k], j, m)) % m
            values.append(val)
            total = (total + val) % m
        
        return total, values
    

    时间复杂度O(n2)O(n^2),适用于子任务1

    方法2:利用FFT思想(大数据)

    由于 nn22 的幂且 qn1(modm)q^n \equiv 1 \pmod{m},我们可以使用快速傅里叶变换的思想。

    关键观察

    ω=q\omega = q,则 ωn1(modm)\omega^n \equiv 1 \pmod{m},且:

    W(ωk)=j=0n1ajωkjW(\omega^k) = \sum_{j=0}^{n-1} a_j \omega^{kj}

    这正是多项式 W(x)W(x) 在单位根 ωk\omega^k 处的取值,可以用快速数论变换(NTT)计算。

    算法步骤

    1. 验证模数:检查 mm 是否支持 NTT(需要存在原根)
    2. 找到原根:如果 mm 是质数,找到模 mm 的原根 gg
    3. 计算变换:执行 NTT 将系数表示转换为点值表示
    def ntt_method(n, m, q, a):
        # 检查 m 是否为质数且存在原根
        if not is_prime(m):
            # 如果 m 不是质数,需要使用其他方法
            return fallback_method(n, m, q, a)
        
        # 找到原根 g
        g = find_primitive_root(m)
        
        # 由于 q^n ≡ 1 mod m,q 应该是 g 的某次幂
        # 即 q ≡ g^k mod m,其中 k = (m-1)/n 的倍数
        
        # 执行 NTT
        values = ntt(a, n, m, g)
        
        # 调整顺序(NTT 输出顺序需要调整)
        total = sum(values) % m
        return total, values
    

    方法3:Chirp Z-变换

    对于更一般的情况,可以使用 Chirp Z-变换:

    $$W(q^k) = q^{-k^2/2} \sum_{j=0}^{n-1} (a_j q^{-j^2/2}) \cdot q^{(k+j)^2/2} $$

    这样就把问题转化为了卷积形式。

    实现细节

    部分分策略

    题目提到:如果和正确但单个值错误,能得 40% 分数。

    这意味着我们可以:

    1. 先专注于高效计算总和 SS
    2. 总和可能比单个值更容易计算

    总和的计算技巧

    $$S = \sum_{k=1}^n W(q^k) = \sum_{k=1}^n \sum_{j=0}^{n-1} a_j q^{kj} = \sum_{j=0}^{n-1} a_j \sum_{k=1}^n q^{kj} $$

    内层求和是等比数列:

    $$\sum_{k=1}^n q^{kj} = \begin{cases} n & \text{if } q^j \equiv 1 \pmod{m} \\ \dfrac{q^{j(n+1)} - q^j}{q^j - 1} & \text{otherwise} \end{cases}$$

    但由于 qn1(modm)q^n \equiv 1 \pmod{m},有 qj(n+1)qj(modm)q^{j(n+1)} \equiv q^j \pmod{m},所以:

    • 如果 qj1q^j \equiv 1,和为 nn
    • 否则,分子为 00,和为 00

    因此:

    S=nj:qj1(modm)ajS = n \cdot \sum_{j: q^j \equiv 1 \pmod{m}} a_j

    由于 qn1q^n \equiv 1,满足 qj1q^j \equiv 1jjnn 的约数。

    最终算法

    对于大数据(n220n \leq 2^{20}

    1. 计算总和

      S=ndnadmodmS = n \cdot \sum_{d|n} a_d \bmod m
    2. 计算单个值

      • 如果 mm 是质数且存在合适的原根,使用 NTT
      • 否则,使用 Chirp Z-变换或分治算法

    代码框架

    #include <bits/stdc++.h>
    using namespace std;
    
    vector<long long> polynomial_evaluation(int n, long long m, long long q, vector<long long>& a) {
        // 1. 计算总和
        long long total = 0;
        for (int d = 0; d < n; d += n / __gcd(n, d)) {
            total = (total + a[d]) % m;
        }
        total = (total * (n % m)) % m;
        
        // 2. 计算每个点的值(使用合适的算法)
        vector<long long> values(n);
        if (n <= 1024) {
            // 小数据:直接计算
            for (int k = 0; k < n; k++) {
                long long val = 0, q_pow = 1;
                for (int j = 0; j < n; j++) {
                    val = (val + a[j] * q_pow) % m;
                    q_pow = (q_pow * q) % m;
                }
                values[k] = val;
            }
        } else {
            // 大数据:使用 NTT 或 Chirp Z-变换
            values = advanced_evaluation(n, m, q, a);
        }
        
        return {total, values};
    }
    

    复杂度分析

    • 总和计算O(n)O(n)
    • 直接计算O(n2)O(n^2),适用于 n210n \leq 2^{10}
    • NTT/Chirp Z-变换O(nlogn)O(n \log n),适用于 n220n \leq 2^{20}

    总结

    算法标签:数论、多项式、快速傅里叶变换、离散数学

    本题的关键在于利用 qn1(modm)q^n \equiv 1 \pmod{m} 的循环性质和 nn22 的幂的特点,将多项式求值问题转化为离散傅里叶变换问题,从而使用快速算法在 O(nlogn)O(n \log n) 时间内解决。

    对于不同的数据范围,需要采用不同的策略:

    • 小数据:直接计算
    • 大数据:NTT 或 Chirp Z-变换
    • 总和计算:利用数论性质简化
    • 1

    信息

    ID
    5396
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    4
    已通过
    1
    上传者