题目链接
题目描述
小红定义一个长度为 的数组
的权值为
。
现在给定一个长度为 的数组
,她想知道所有子数组的权值和是多少?答案对
取模。
解题思路
这是一个求解所有子数组特定属性之和的问题。直接枚举所有子数组并计算权值的 或
算法会超时,因此我们需要寻找一种更高效的计算方法。
贡献法
解决这类问题的常用技巧是贡献法。我们不去计算每个子数组的权值,而是反过来,计算原始数组中的每一个元素 对最终的总和贡献了多少。最终答案就是所有元素贡献的总和。
推导单个元素的贡献
让我们来分析单个元素 (使用 0-based 索引,
从
到
)的总贡献。
-
何时产生贡献?
当且仅当一个子数组包含
时,
才会对该子数组的权值有贡献。一个子数组
a[l...r]
包含的充要条件是
。
-
在子数组中的贡献
在子数组
a[l...r]
中,是第
个元素(1-based 索引)。因此,它对这个子数组权值的贡献是
。
-
的总贡献
的总贡献是它在所有包含它的子数组中的贡献之和。
-
化简公式
-
将
提出来:
-
括号中的系数部分,内层求和项
与
无关。对于一个固定的
,终点
有
种选择(从
到
)。因此,内层求和的结果是
。
-
公式变为:
-
再提出与
无关的
:
-
最后的求和部分
是
的和,即
。
-
最终,我们得到
对总权值和的完整贡献:
-
最终算法
总权值和就是所有元素贡献的和:
这个公式可以在 的时间内完成计算。在实现时,所有计算都需要对
取模,除以 2 的操作要用乘以 2 的模逆元(即
)来代替。
代码
#include <iostream>
#include <vector>
#include <numeric>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n;
cin >> n;
vector<long long> a(n);
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
long long total_weight_sum = 0;
long long MOD = 1e9 + 7;
long long inv2 = 500000004;
for (int i = 0; i < n; ++i) {
long long val = a[i];
long long n_ll = n;
long long i_ll = i;
long long term1 = (n_ll - i_ll);
long long term2 = (i_ll + 1);
long long term3 = (i_ll + 2);
long long coeff = (term1 % MOD * (term2 % MOD)) % MOD;
coeff = (coeff * (term3 % MOD)) % MOD;
coeff = (coeff * inv2) % MOD;
long long contribution = (val % MOD * coeff) % MOD;
total_weight_sum = (total_weight_sum + contribution) % MOD;
}
cout << total_weight_sum << endl;
return 0;
}
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
long[] a = new long[n];
for (int i = 0; i < n; i++) {
a[i] = sc.nextLong();
}
long totalWeightSum = 0;
long MOD = 1_000_000_007;
long inv2 = 500000004;
for (int i = 0; i < n; i++) {
long val = a[i];
long n_ll = n;
long i_ll = i;
long term1 = n_ll - i_ll;
long term2 = i_ll + 1;
long term3 = i_ll + 2;
long coeff = (term1 % MOD * (term2 % MOD)) % MOD;
coeff = (coeff * (term3 % MOD)) % MOD;
coeff = (coeff * inv2) % MOD;
long contribution = (val % MOD * coeff) % MOD;
totalWeightSum = (totalWeightSum + contribution) % MOD;
}
System.out.println(totalWeightSum);
}
}
import sys
def solve():
try:
n = int(sys.stdin.readline())
a = list(map(int, sys.stdin.readline().split()))
except (IOError, ValueError):
return
total_weight_sum = 0
MOD = 10**9 + 7
inv2 = 500000004
for i in range(n):
val = a[i]
# Using 0-based index i
term1 = n - i
term2 = i + 1
term3 = i + 2
coeff = (term1 * term2) % MOD
coeff = (coeff * term3) % MOD
coeff = (coeff * inv2) % MOD
contribution = (val * coeff) % MOD
total_weight_sum = (total_weight_sum + contribution) % MOD
print(total_weight_sum)
solve()
算法及复杂度
-
算法:贡献法、数学推导
-
时间复杂度:
。
- 我们只需要遍历一次数组,每次循环中的计算都是常数时间的操作。
-
空间复杂度:
。
- 需要
的空间来存储输入的数组。如果输入可以边读边处理,则空间复杂度可以降为
。
- 需要