题解:洛谷 AT_abc468_e Sum of Average

【题目来源】

洛谷:AT_abc468_e AT_abc468_e [ABC468E] Sum of Average

【题目描述】

给定一个正整数 \(N\) 和一个长度为 \(N\) 的整数序列 \(A=(A_1,A_2,\ldots,A_N)\)

定义 \(f(l,r)\)\(A_l,A_{l+1},\ldots,A_r\) 的算术平均值。

\[\sum_{1\le l\le r\le N} f(l,r) \]

\(998244353\) 取模后的值。

关于模 \(998244353\) 意义下的有理数定义:

在该问题的约束下,可以证明所求的有理数总可以表示为最简分数 \(\frac{P}{Q}\),且满足 \(Q \not\equiv 0 \pmod{998244353}\)。因此,存在唯一的整数 \(R\),使得

\[R \times Q \equiv P \pmod{998244353},\quad 0 \le R < 998244353 \]

请输出这个 \(R\)

【输入】

输入按以下格式从标准输入给出:

\(N\)
\(A_1\) \(A_2\) \(\ldots\) \(A_N\)

【输出】

输出答案。

【输入样例】

2
2 3

【输出样例】

499122184

【核心思想】

  1. 问题分析:给定长度为 \(N\) 的序列 \(A\),求所有子区间 \([l,r]\) 的平均值之和,对 \(998244353\) 取模。\(f(l,r) = \frac{\sum_{i=l}^{r} A_i}{r-l+1}\)。直接枚举 \(O(N^2)\) 个子区间不可行,需要数学优化。这是一个前缀和 + 组合计数 + 模逆元问题,核心在于将求和式按区间长度分类,利用前缀和的前缀和快速计算同长度区间的元素和之和。

  2. 算法选择

    • 前缀和的前缀和:设 \(s[i] = \sum_{j=1}^{i} A_j\)\(ds[i] = \sum_{j=1}^{i} s[j]\)
    • 按长度分类求和:对于固定长度 \(len\),所有长度为 \(len\) 的区间的元素和之和可通过 \(ds\) 数组 \(O(1)\) 计算
    • 模逆元:利用费马小定理计算除法在模 \(998244353\) 下的逆元
  3. 关键步骤

    • 读入数据:读取 \(N\) 和数组 \(A[1..N]\)
    • 预处理前缀和
      • \(s[i] = (s[i-1] + A_i) \bmod MOD\)
      • \(ds[i] = (ds[i-1] + s[i]) \bmod MOD\)
    • 按区间长度枚举\(len\)\(1\)\(N\)):
      • 计算所有长度为 \(len\) 的区间的元素和之和:
        • \(\Delta = ds[N] - ds[len-1] - ds[N-len]\)
        • 解释:\(\sum_{l=1}^{N-len+1} \sum_{i=l}^{l+len-1} A_i = \sum_{l=1}^{N-len+1} (s[l+len-1] - s[l-1]) = (ds[N] - ds[len-1]) - ds[N-len]\)
      • 长度为 \(len\) 的区间的平均值之和 = \(\Delta \times inv(len) \bmod MOD\)
      • \(ans \leftarrow (ans + \Delta \times inv(len)) \bmod MOD\)
    • 输出结果\((ans \bmod MOD + MOD) \bmod MOD\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \log MOD)\),预处理 \(O(N)\),枚举长度 \(O(N)\),每次快速幂求逆元 \(O(\log MOD)\)
    • 空间复杂度:\(O(N)\),前缀和数组
  5. 前缀和的前缀和与模逆元的核心思想

    • 区间和的双重求和:对于固定长度 \(len\),所有区间的和之和为 \(\sum_{l=1}^{N-len+1} (s[l+len-1] - s[l-1])\)。利用 \(ds\) 数组,\(\sum_{l=1}^{N-len+1} s[l+len-1] = ds[N] - ds[len-1]\)(错位相消),\(\sum_{l=1}^{N-len+1} s[l-1] = ds[N-len]\),因此 \(\Delta = ds[N] - ds[len-1] - ds[N-len]\)
    • 模逆元替代除法:在模质数 \(998244353\) 下,除以 \(len\) 等价于乘以 \(len^{MOD-2} \bmod MOD\),利用快速幂在 \(O(\log MOD)\) 内计算
    • 负数取模处理:减法可能导致负数,通过 (x % MOD + MOD) % MOD 确保结果在 \([0, MOD)\) 范围内
    • 组合恒等式的应用:将 \(O(N^2)\) 的区间枚举转化为 \(O(N)\) 的长度枚举,通过前缀和的前缀和实现 \(O(1)\) 的单长度查询
    • 适用于大规模区间统计、模意义下的有理数计算、组合数学优化类问题

【解题思路】

![[7f26b9dde8eff6126994ba6d6daf2357.png|539]]

【算法标签】

普及+ #前缀和

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long    // 将int定义为long long,避免中间计算溢出
const int N = 500005, MOD = 998244353;  // N为数组最大容量,MOD为模数998244353
int n, ans;             // n为序列长度,ans存储最终答案
int a[N], s[N], ds[N];  // a存储原数组;s为前缀和数组(s[i]=A_1+...+A_i);ds为前缀和的前缀和(ds[i]=s[1]+...+s[i])

// 快速幂算法:计算a的b次幂对MOD取模的结果
// 用于求逆元:根据费马小定理,i的逆元为i^(MOD-2) mod MOD(MOD为质数)
int power(int a, int b)
{
    int res = 1;        // res存储幂的结果
    a %= MOD;           // 先将底数对MOD取模
    while (b > 0)       // 当指数b大于0时继续循环
    {
        if (b & 1)      // 如果b的最低位为1(即b为奇数)
        {
            res = res * a % MOD;  // 将当前底数乘入结果
        }
        a = a * a % MOD;  // 底数平方(为下一位做准备)
        b >>= 1;          // 指数右移一位(相当于除以2)
    }
    return res;           // 返回a^b mod MOD
}

signed main()           // 使用signed main配合#define int long long
{
    cin >> n;           // 读入序列长度N
    for (int i = 1; i <= n; i++)  // 读入N个整数
    {
        cin >> a[i];
        // 同时计算前缀和s和前缀和的前缀和ds
        s[i] = (s[i - 1] + a[i]) % MOD;    // s[i] = (A_1 + A_2 + ... + A_i) mod MOD
        ds[i] = (ds[i - 1] + s[i]) % MOD;  // ds[i] = (s[1] + s[2] + ... + s[i]) mod MOD
    }
    // 枚举区间长度i(从1到N),计算所有长度为i的区间的平均值之和
    for (int i = 1; i <= n; i++)  // i表示当前枚举的区间长度
    {
        // delta = ds[N] - ds[i-1] - ds[N-i]
        // 推导:所有长度为i的区间的和 = 以每个位置结尾的长度为i的区间和之和
        // 利用前缀和的前缀和ds快速计算
        int delta = ds[n] - ds[i - 1] - ds[n - i];
        // 处理负数:先取模再加MOD再取模,确保结果在[0, MOD)范围内
        delta = (delta % MOD + MOD) % MOD;

        int invi = power(i, MOD - 2);  // 计算i在模MOD下的逆元(费马小定理)
        // 所有长度为i的区间的平均值之和 = delta / i = delta * invi mod MOD
        ans = (ans + delta * invi) % MOD;  // 累加到总答案中
    }

    // 确保答案非负:先取模再加MOD再取模
    cout << (ans % MOD + MOD) % MOD << endl;  // 输出最终答案
    return 0;
}

【运行结果】

2
2 3
499122184
posted @ 2026-07-28 20:19  团爸讲算法  阅读(12)  评论(0)    收藏  举报