题目链接

子数列求积

题目描述

给定一个长度为 的正整数序列 。接下来有 次独立查询,第 次查询给出一对下标 (),请你计算区间乘积 并对模数 取模后的结果。

解题思路

本题要求我们高效地计算多次给定区间的乘积。如果对每次查询都遍历区间 进行计算,在查询次数 和区间长度都很大的情况下,总时间复杂度会达到 ,这通常会超出时间限制。

为了优化查询效率,我们可以预处理信息。类似于前缀和,我们可以使用 前缀积 的思想。

  1. 前缀积数组

    我们定义一个前缀积数组 ,其中 存储序列 个元素的乘积模 的结果: [ P[i] = \prod_{k=1}^{i} a_k \pmod{10^9+7} ] 这个数组可以 的时间内计算出来,递推公式为 ,并定义

  2. 区间乘积计算

    利用前缀积数组,区间 的乘积可以表示为: [ \prod_{i=l}^{r} a_i = \frac{\prod_{i=1}^{r} a_i}{\prod_{i=1}^{l-1} a_i} = \frac{P[r]}{P[l-1]} ] 在模算术中,除以一个数等价于乘以这个数的 模逆元。设模数为 。区间乘积的计算就转换为: [ \left( P[r] \cdot (P[l-1])^{-1} \right) \pmod M ] 其中 关于模 的乘法逆元。

  3. 求解模逆元

    因为模数 是一个质数,我们可以根据 费马小定理 来求解模逆元。费马小定理指出,如果 是一个质数,而整数 不是 的倍数,则有

    由此可推导出

    所以, 可以通过计算 得到。这个幂运算可以通过 快速幂算法 的时间内高效完成。

  4. 算法流程总结

    • 预处理:用 时间计算出前缀积数组
    • 查询:对于每个查询 ,用 时间计算 的模逆元,然后与 相乘得到最终答案。

    总的时间复杂度为 ,空间复杂度为 ,能够满足性能要求。

代码

#include <iostream>
#include <vector>

using namespace std;

// 定义模数
const int MOD = 1e9 + 7;

// 快速幂函数,计算 (base^exp) % MOD
long long power(long long base, long long exp) {
    long long res = 1;
    base %= MOD;
    while (exp > 0) {
        if (exp % 2 == 1) {
            res = (res * base) % MOD;
        }
        base = (base * base) % MOD;
        exp /= 2;
    }
    return res;
}

// 模逆元函数,计算 a^{-1} mod MOD
long long modInverse(long long n) {
    return power(n, MOD - 2);
}

int main() {
    int n, q;
    // 读取序列长度和查询数量
    cin >> n >> q;

    vector<long long> prefix_prod(n + 1, 1);
    for (int i = 1; i <= n; ++i) {
        long long val;
        cin >> val;
        // 计算前缀积
        prefix_prod[i] = (prefix_prod[i - 1] * val) % MOD;
    }

    vector<long long> results;
    for (int i = 0; i < q; ++i) {
        int l, r;
        cin >> l >> r;
        // 计算 P[l-1] 的模逆元
        long long inv_prefix_l_minus_1 = modInverse(prefix_prod[l - 1]);
        // 计算区间乘积
        long long ans = (prefix_prod[r] * inv_prefix_l_minus_1) % MOD;
        results.push_back(ans);
    }

    for (int i = 0; i < results.size(); ++i) {
        cout << results[i] << (i == results.size() - 1 ? "" : " ");
    }
    cout << endl;

    return 0;
}
import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;

public class Main {
    // 定义模数
    private static final int MOD = 1_000_000_007;

    // 快速幂函数,计算 (base^exp) % MOD
    private static long power(long base, long exp) {
        long res = 1;
        base %= MOD;
        while (exp > 0) {
            if (exp % 2 == 1) {
                res = (res * base) % MOD;
            }
            base = (base * base) % MOD;
            exp /= 2;
        }
        return res;
    }

    // 模逆元函数,计算 n^{-1} mod MOD
    private static long modInverse(long n) {
        return power(n, MOD - 2);
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int q = sc.nextInt();

        long[] prefix_prod = new long[n + 1];
        prefix_prod[0] = 1;
        for (int i = 1; i <= n; ++i) {
            long val = sc.nextLong();
            // 计算前缀积
            prefix_prod[i] = (prefix_prod[i - 1] * val) % MOD;
        }

        List<Long> results = new ArrayList<>();
        for (int i = 0; i < q; ++i) {
            int l = sc.nextInt();
            int r = sc.nextInt();
            // 计算 P[l-1] 的模逆元
            long inv_prefix_l_minus_1 = modInverse(prefix_prod[l - 1]);
            // 计算区间乘积
            long ans = (prefix_prod[r] * inv_prefix_l_minus_1) % MOD;
            results.add(ans);
        }

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < results.size(); ++i) {
            sb.append(results.get(i));
            if (i < results.size() - 1) {
                sb.append(" ");
            }
        }
        System.out.println(sb.toString());
    }
}
# 定义模数
MOD = 10**9 + 7

def main():
    # 读取序列长度和查询数量
    n, q = map(int, input().split())
    
    # 读取序列
    a = list(map(int, input().split()))

    # 计算前缀积
    prefix_prod = [1] * (n + 1)
    for i in range(n):
        prefix_prod[i + 1] = (prefix_prod[i] * a[i]) % MOD
    
    results = []
    for _ in range(q):
        l, r = map(int, input().split())
        
        # 使用 pow(base, exp, mod) 进行快速幂和模逆元计算
        # 计算 P[l-1] 的模逆元
        inv_prefix_l_minus_1 = pow(prefix_prod[l - 1], MOD - 2, MOD)
        
        # 计算区间乘积
        ans = (prefix_prod[r] * inv_prefix_l_minus_1) % MOD
        results.append(ans)
        
    print(*results)

if __name__ == "__main__":
    main()

算法及复杂度

  • 算法:前缀积 + 模逆元 (费马小定理) + 快速幂
  • 时间复杂度:预处理前缀积数组需要 。每次查询需要通过快速幂计算模逆元,耗时 ,其中 是模数。总共有 次查询,因此总时间复杂度为
  • 空间复杂度:需要一个数组来存储前缀积,所以空间复杂度为