P5431 【模板】模意义下的乘法逆元 2

题目描述


给定 \(n\) 个正整数 \(a_i\) ,求它们在模 \(p\) 意义下的乘法逆元。
由于输出太多不好,所以将会给定常数 \(k\),你要输出的答案为:

\[\sum\limits_{i=1}^n\frac{k^i}{a_i} \]

答案对 \(p\) 取模。

  • 线性递推公式\(i^{-1}=(p-\lfloor{p\over i})\rfloor\times (p\% i)^{-1}\pmod{p}\),可以以\(O(n)\)的时间复杂度求解\(1\sim n\)的连续正整数的逆元。详细参考:P3811 【模板】模意义下的乘法逆元 - chen_xing - 博客园
  • 针对于这道题目而言,数据范围是\(2≤k<p≤10^9,1≤ai​<p\),如果强制保证我们求解连续正整数的逆元的话,我们的时间复杂度是\(O(max\{a_1,a_2,\cdots,a_n\})<O(p)\),大概率是会超出时间限制的。
  • 我们需要再找到一个可以不要求连续的\(O(n)\)时间复杂度的算法。

前缀积求解序列逆元


  • 假设我们有一个序列\(a=[a_1,a_2,\cdots,a_n]\),我们需要在\(O(n)\)甚至更低的时间复杂度下求解\([a_1^{-1}\pmod{p},a_2^{-1}\pmod{p},\cdots,a_n^{-1}\pmod{p}]\)。
  • 我们可以先计算\(a_{prod}\equiv\prod_{i=1}^na_i\pmod{p}\),该过程时间复杂度为\(O(n)\);
  • 计算\(a_{prod}^{-1}\equiv(a_1\times a_2\times \cdots\times a_n)^{-1}\pmod{p}\)。这一步我们可以利用费马小定理或者扩展欧几里得定理求解,时间复杂度为\(O(log\ p)\);
  • 如果我们维护一个前缀积数组s,其中\(s_i=\prod_{j=1}^i a_j\pmod{p}\),那么当我们以下面的流程从后向前进行遍历时,我们就可以准确求出\(a_i^{-1}\pmod{p}\):

    \[\begin{flalign}& for\ i=n\ down\ to\ 1:\\&\quad\quad a_i^{-1}\equiv a_{prod}^{-1}\times s_{i-1}\equiv {a_1\times a_2\times\cdots \times a_{i-1}\over a_1\times a_2\times\cdots\times a_{i}}\equiv a_i^{-1}\pmod{p}\\& \quad\quad a_{prod}\equiv a_{prod}\times a_i\pmod{p}\end{flalign} \]

    该过程时间复杂度为\(O(n)\),空间复杂度为\(O(n)\)。
  • 注意我们要求解的最终结果是\(\sum_{i=1}^n{k^i\over a_i}\pmod{p}\),我们把求和公式展开,提取公因式\(k\):

    \[{k\over a_1}+{k^2\over a_2}+\cdots+{k^n\over a_n}\equiv k({1\over a_1}+k({1\over a_2}+\cdots+k({1\over a_n})\cdots))\pmod{p} \]

    这意味着我们在求解\(a_i^{-1}\)的时候就可以直接进行对结果贡献的累加,无需再开辟空间存储\(a_i^{-1}\pmod{p}\)了。
#include <iostream>
#include <cstdio>

using namespace std;

const int MAXN = 5000005;
int a[MAXN];
int s[MAXN];

// fread 快速模板
char buf[1<<21], *p1 = buf, *p2 = buf;
inline char gc() {
    if (p1 == p2) {
        p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin);
        if (p1 == p2) return EOF;
    }
    return *p1++;
}

inline int read() {
    int x = 0;
    char c = gc();
    while (c < '0' || c > '9') c = gc();
    while (c >= '0' && c <= '9') {
        x = (x * 10) + (c - '0');
        c = gc();
    }
    return x;
}

int qpow(int base, int exp, int mod) {
    int res = 1;
    base %= mod;
    while (exp > 0) {
        if (exp % 2 == 1) res = 1LL * res * base % mod;
        base = 1LL * base * base % mod;
        exp >>= 1;
    }
    return res;
}

int main() {
    int n = read();
    int p = read();
    int k = read();
    s[0] = 1;
    for (int i = 1; i <= n; i++) {
        a[i] = read();
        s[i] = 1LL * s[i - 1] * a[i] % p;
    }
    // a ** (p - 1) equiv 1 (mod p) → a ** (p - 2) * a equiv 1 (mod p)
    int aProdInv = qpow(s[n], p - 2, p);

    int ans = 0;
    for (int i = n; i > 0; i--) {
        int aInv = 1LL * aProdInv * s[i - 1] % p;
        ans = (1LL * ans * k + aInv) % p;
        aProdInv = 1LL * aProdInv * a[i] % p;
    }
    ans = 1LL * ans * k % p;
    printf("%d\n", ans);
    return 0;
}
  • 时间复杂度:\(O(n + log\ p)\rightarrow O(n)\)
  • 空间复杂度:\(O(n)\)
posted @ 2026-09-08 13:30  chen_xing  阅读(11)  评论(0)    收藏  举报