题目
给定一个最初为空的数组,您需要执行 q 操作:
• 给定两个非负整数t 和v,从数组末尾取出元素t 次,然后将v 追加到数组末尾。在这次操作之前保证t不超过数组的长度。
每次运算结束后,设a1,a2,…,an为当前数组,求S1,S2,…,Sn之和,其中si+an为从位置i开始的后缀之和。艾+艾+1+
由于答案可能非常大,因此输出它们对 1000 000 007 取模。
第一行包含一个整数q(1≤q≤5×105),表示操作次数。
接下来的q行包含两个非负整数t和v(0≤v≤10°),描述一个操作,其中t不超过该操作之前的数组
长度。
输出q行,每行包含一个整数,表示答案。
题目分析
分析发现,每一次都是求一个新数组sum下面是sum的表达公式
sum = ∑ i = 1 n i ⋅ a i \text{sum} = \sum_{i=1}^n i \cdot a_i sum=i=1∑ni⋅ai
但是每一次这个新数组都是上一个数组的前一部分截取下来,加一个新的尾部,因此可以每次询问时操作一次O(1)的时间复杂度。
重点在于两点
- 用前缀和数组,这样可以避免重复操作
- 取模的时候尽量多取模,这个后面会细说
解答
完整代码
Unimportant parts
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define For for (ll i = 1; i <= n; i++)
#define rFor for (ll i = n; i > 0; i--)
#define rep(i, sta, end) for (ll i = sta; i <= end; i++)
#define rrep(i, end, sta) for (ll i = end; i >= sta; i--)
#define All(x) for (auto item : x)
int main() {
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int num = 1;
//cin>>num;
while (num--)
solve();
return 0;
}
key part
inline void solve() {
ll ans = 0;
ll Mod = 1e9 + 7;
int q;
cin >> q;
ll idx = 1;
vector<ll> arr(5e5 + 10, 0);
while (q--) {
int t, v;
cin >> t >> v;
idx -= t;
arr.at(idx) = (arr.at(idx - 1) + 1LL* (idx) * v % Mod)%Mod;
cout<< arr[ idx ]%Mod <<endl;
idx++;
}
}
算法分析
对比一下我错的代码
inline void solve() {
ll ans = 0;
ll Mod = 1e9 + 7;
int q;
cin >> q;
ll idx = 1;
vector<ll> arr(5e5 + 10, 0);
while (q--) {
int t, v;
cin >> t >> v;
idx -= t;
arr.at(idx) = arr.at(idx - 1) + 1LL* (idx) * v;
cout<< arr[ idx ]%Mod <<endl;
idx++;
}
}
只有一行代码不一样
arr.at(idx) = arr.at(idx - 1) + 1LL* (idx) * v;
和
arr.at(idx) = (arr.at(idx - 1) + 1LL* (idx) * v % Mod)%Mod;
代码重点分析(可无)
本题不算难,代码实现也没问题,主要带来的是一些编程习惯上的思考:
- 在每一道题有思路的时候,最好先停下来思考一下这道题代码实现上的特点,比如这道题发现有一部分前缀数组的和不需要重复计算,就可以使用前缀和结构。这么做可以及时使用有效数据结构大大减少不必要的复杂度以及代码实现困难度。
- 取模的习惯,前面已经提过了。