小S的子序列平均数之和题解 | 豆包MarsCode AI刷题

52 阅读3分钟

题目描述

小S得到了一个由 n 个元素组成的数组,她想求出所有子序列的平均数之和。由于子序列的数量非常多,因此需要对结果取模 109+710^9+7 来避免结果过大。子序列是从原数组中选择部分元素,保持原数组的顺序形成的新数组。 你需要输出所有子序列的平均数之和对 109+710^9+7 取模的值。

题目分析

公式推导

子序列是从原数组中选择部分元素,保持原数组顺序形成的新数组。对于一个长度为 nn 的数组,其子序列的数量为 2n2^n。如果直接暴力枚举每个子序列并计算其平均数之和,时间复杂度为 O(n2n)O(n2^n),即使 nn 较小,这种方法也会非常耗时。因此,我们需要换一个思路,避免直接遍历所有子序列。

为了优化计算,我们可以将问题转化为统计数组中每个元素对最终结果的贡献度。具体而言,每个元素 aia_i 在不同长度的子序列中出现的次数不同,其贡献可以用数学公式直接累加。

对每个元素aia_i,它可能出现在长度为k{1,2,,n}k\in\{1,2,\dots,n\}的子序列中,由于需要求平均数,在这个序列中,aia_i对最终结果的贡献为1kai\frac{1}{k}a_i。那么,aia_i一共可以出现在多少个长度为kk的子序列中呢?由于已经选择了aia_i,还需要从剩下的n1n-1个数中选k1k-1个数,共Cn1k1C_{n-1}^{k-1}种选法。

因此,aia_i对答案的总贡献为:aik=1n1kCn1k1a_i\sum_{k=1}^n\frac{1}{k}C_{n-1}^{k-1}

最终的答案应为:i=1naik=1n1kCn1k1\sum_{i=1}^na_i\sum_{k=1}^n\frac{1}{k}C_{n-1}^{k-1}

算法实现

在这道题中,为了高效计算组合数,我们需要用到数论逆元的概念。数论逆元是一种快速求解模运算下逆元的方法。对于一个整数 aa 和一个质数 pp,如果存在一个整数 bb,使得 ab1 (mod p)a \cdot b \equiv 1 \ (\text{mod} \ p),那么 bb 就被称为 aa 在模 pp 意义下的逆元,记作 a1modpa^{-1} \mod p。数论逆元最大的作用是,可以将分数1a\frac{1}{a}在mod p的意义下视作整数,从而将有理数的加减乘除运算全部转换到mod p的整数域中。

那么如何快速地计算数论逆元呢?一种常见的方法是利用数论中的费马小定理:如果 pp 是质数,则对于任何不被 pp 整除的整数 aa,则有,ap11 (mod p)a^{p-1} \equiv 1 \ (\text{mod} \ p),将 aa 两边同时除以 aa,可以得到:ap2a1 (mod p)a^{p-2} \equiv a^{-1} \ (\text{mod} \ p),如果使用快速幂,可以在O(logp)O(\log p)的时间复杂度内求得一个数的逆元。但是,在本题中,我们需要求1,2,,n1,2,\dots,n的逆元,这会让时间复杂度变为O(nlogp)O(n\log p),有没有更快速的求法呢?

对于k{1,2,,n}k\in\{1,2,\dots,n\},我们设pp除以kk的商和余数是a,ba,b,则有p=ak+bp=ak+b,对等式两边同时mod p,有ak+b0 (mod p)ak+b \equiv 0\ (\text{mod} \ p)。整理可得1kab (mod p)\frac{1}{k} \equiv -\frac{a}{b}\ (\text{mod} \ p),即k1ab1 (mod p)k^{-1} \equiv -ab^{-1}\ (\text{mod} \ p)。因此,只要能知道bb的逆元,就能快速地求得kk的逆元。由于余数bb是小于kk的,只要依次求k{1,2,,n}k\in\{1,2,\dots,n\}的逆元,一定能得到bb的逆元,这样,就能在O(n)O(n)的时间求1,2,,n1,2,\dots,n的逆元了。

注意到,Cnk=n!k!(nk)!C_{n}^{k}=\frac{n!}{k!(n-k)!},我们提前计算三个数组frac,inv,inv_frac,分别代表i!i!,1i\frac{1}{i},1i!\frac{1}{i!}在mod p意义下对应的整数,这样,就能以O(1)O(1)的时间求1kCn1k1\frac{1}{k}C_{n-1}^{k-1}了。

具体细节参考下面的代码:

代码

mod = 10**9+7
def solution(n: int, arr: list) -> int:
    frac = [1]*(n+1)
    for i in range(1,n+1):
        frac[i]=frac[i-1]*i % mod
    inv=[1]*(n+1)
    for i in range(2,n+1):
        inv[i]=(mod-mod//i)*inv[mod%i]%mod
    inv_frac=[1]*(n+1)
    for i in range(1,n+1):
        inv_frac[i]=inv_frac[i-1]*inv[i]%mod
    f=0
    for i in range(1,n+1):
        f+=inv[i]*frac[n-1]%mod*inv_frac[i-1]%mod*inv_frac[n-i]%mod
        f%=mod
    ans=f*sum(arr)%mod
    return ans