题目描述
小S得到了一个由 n 个元素组成的数组,她想求出所有子序列的平均数之和。由于子序列的数量非常多,因此需要对结果取模 来避免结果过大。子序列是从原数组中选择部分元素,保持原数组的顺序形成的新数组。 你需要输出所有子序列的平均数之和对 取模的值。
题目分析
公式推导
子序列是从原数组中选择部分元素,保持原数组顺序形成的新数组。对于一个长度为 的数组,其子序列的数量为 。如果直接暴力枚举每个子序列并计算其平均数之和,时间复杂度为 ,即使 较小,这种方法也会非常耗时。因此,我们需要换一个思路,避免直接遍历所有子序列。
为了优化计算,我们可以将问题转化为统计数组中每个元素对最终结果的贡献度。具体而言,每个元素 在不同长度的子序列中出现的次数不同,其贡献可以用数学公式直接累加。
对每个元素,它可能出现在长度为的子序列中,由于需要求平均数,在这个序列中,对最终结果的贡献为。那么,一共可以出现在多少个长度为的子序列中呢?由于已经选择了,还需要从剩下的个数中选个数,共种选法。
因此,对答案的总贡献为:
最终的答案应为:
算法实现
在这道题中,为了高效计算组合数,我们需要用到数论逆元的概念。数论逆元是一种快速求解模运算下逆元的方法。对于一个整数 和一个质数 ,如果存在一个整数 ,使得 ,那么 就被称为 在模 意义下的逆元,记作 。数论逆元最大的作用是,可以将分数在mod p的意义下视作整数,从而将有理数的加减乘除运算全部转换到mod p的整数域中。
那么如何快速地计算数论逆元呢?一种常见的方法是利用数论中的费马小定理:如果 是质数,则对于任何不被 整除的整数 ,则有,,将 两边同时除以 ,可以得到:,如果使用快速幂,可以在的时间复杂度内求得一个数的逆元。但是,在本题中,我们需要求的逆元,这会让时间复杂度变为,有没有更快速的求法呢?
对于,我们设除以的商和余数是,则有,对等式两边同时mod p,有。整理可得,即。因此,只要能知道的逆元,就能快速地求得的逆元。由于余数是小于的,只要依次求的逆元,一定能得到的逆元,这样,就能在的时间求的逆元了。
注意到,,我们提前计算三个数组frac,inv,inv_frac,分别代表,,在mod p意义下对应的整数,这样,就能以的时间求了。
具体细节参考下面的代码:
代码
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