📜  如果给定T(n)且n非常大,则序列求和(1)

📅  最后修改于: 2023-12-03 15:25:01.383000             🧑  作者: Mango

序列求和

在算法和数据结构中,序列求和是一个经常出现的问题。假设有一个序列a1,a2,...,an,计算它们的和。我们可以用循环来实现,也可以使用递归来实现。但是,如果n非常大,这些实现都会很慢。因此,我们需要一种更快的方法来计算序列的和。

前缀和

前缀和是一种预处理技巧,可以在O(n)时间内计算出序列的任何子段的和。具体来说,对于序列a1,a2,...,an,我们定义前缀和数组s1,s2,...,sn,其中si=a1+a2+...+ai。因此,si表示序列前i个数的和。可以使用循环来计算前缀和数组s。

s = [0] * (n + 1)
for i in range(1, n + 1):
    s[i] = s[i - 1] + a[i]

计算子段和时,我们可以使用前缀和数组。例如,要计算子段[l, r]的和,只需计算s[r]-s[l-1]即可。

分块

分块是一种常用的算法,可以在O(sqrt(n))时间内计算序列的任何子段的和。具体来说,我们将序列分成sqrt(n)个块,每个块的大小为sqrt(n),并预处理每个块的和。我们定义一个数组b,其中bi表示第i个块的和。对于每个块,我们使用前缀和来计算bi。

b = [0] * (int(n**0.5) + 1)
for i in range(1, len(b)):
    left = (i - 1) * int(n**0.5) + 1
    right = min(i * int(n**0.5), n)
    for j in range(left, right + 1):
        b[i] += a[j]

计算子段和时,我们首先找到左端点和右端点所属的块,并分别计算它们的部分和。接下来,我们在两个块之间计算所有完整块的和。最后,我们计算左右两端剩下的部分,即[l, L-1]和[R+1, r],并将它们的和加起来。

def query(l, r):
    ans = 0
    if block[l] == block[r]:
        for i in range(l, r + 1):
            ans += a[i]
    else:
        for i in range(l, (block[l] + 1) * int(n**0.5)):
            ans += a[i]
        for i in range(block[l] + 1, block[r]):
            ans += b[i]
        for i in range(block[r] * int(n**0.5), r + 1):
            ans += a[i]
    return ans
反演

反演是一种利用数学公式来计算序列和的技巧。具体来说,我们需要从一般的形式转换为特殊的形式,并利用特殊形式的公式来计算序列和。在这里,我们介绍两个常用的反演公式:莫比乌斯反演和欧拉反演。

莫比乌斯反演:假设f和g是两个函数,它们都在正整数n上有定义。我们定义它们的卷积为h(n)=sum(f(d)*g(n/d)),其中d是n的因子。莫比乌斯反演公式是:

f(n)=sum(mu(d)*h(n/d)), d|n

其中mu是莫比乌斯函数,它的定义如下:

mu(1)=1,

mu(d)=(-1)^k,如果d可以表示成质数的乘积,且有k个质数因子。

否则mu(d)=0。

欧拉反演:假设f和g是两个函数,它们都在正整数n上有定义。我们定义它们的卷积为h(n)=sum(f(d)*g(n/d)),其中d是n的因子。欧拉反演公式是:

g(n)=sum(f(d)*mu(n/d)), d|n

反演可以将一般的问题转化为特殊的问题,从而利用数学公式来计算序列和。反演技巧在组合数学、数论和图论中都很有作用。

结论

序列求和是一个基本的问题,在算法和数据结构中经常出现。对于不同规模的问题,我们可以使用不同的算法来计算序列的和。前缀和和分块是两种常用的算法,它们都可以在O(sqrt(n))时间内计算序列的任何子段的和。反演是一种利用数学公式来计算序列和的技巧,它在组合数学、数论和图论中都有很多应用。