Description:
每组样例给定一个数列,求下列结果,答案对
1
e
9
+
7
1e9+7
1e9+7 取模。
∑
i
=
1
n
∑
l
=
1
i
∑
r
=
i
n
∑
k
=
l
r
a
k
\sum_{i=1}^n \sum_{l=1}^i \sum_{r=i}^n \sum_{k=l}^ra_k
i=1∑nl=1∑ir=i∑nk=l∑rak
Constrains:
- 1 ≤ T ≤ 1 0 5 1\leq T \leq 10^5 1≤T≤105
- 1 ≤ n ≤ 1 0 5 1\leq n \leq 10^5 1≤n≤105
- 0 ≤ a i ≤ 1 0 9 0 \leq a_i \leq10^9 0≤ai≤109
- ∑ n ≤ 2 × 1 0 5 \sum n \leq 2\times 10^5 ∑n≤2×105
Sample IO:
2 80
4 90
2 0 2 3
4
1 0 2 5
Analysis1:
- 首先想到,能不能很简单的计算出每个数的贡献次数,如果不容易想,可以打表观察一下对应不同 n n n 情况下每个下标 p p p 对应的贡献次数(方法2详见下文)。
- 方法一:假若每个数的贡献次数数学规律不好找,那可以尝试从每个区间的贡献次数考虑,最后换算成每个数的贡献。
- 尝试对原式进行拆解,进行数学处理,不难发现需要用到前缀和,进而用到二位前缀和。其实,这个思路可以理解为
∑ i = 1 n ∑ l = 1 i ∑ r = i n ∑ k = l r a k = ∑ i = 1 n ∑ l = 1 i ∑ r = i n s [ r ] − s [ l − 1 ] ∑ i = 1 n ∑ l = 1 i s s [ n ] − s s [ i − 1 ] − ∑ i = 1 n ∑ l = 1 i ( n − i + 1 ) × s [ l − 1 ] = ∑ i = 1 n i × ( s s [ n ] − s s [ i − 1 ] ) − ∑ i = 1 n ( n − i + 1 ) × s s [ i − 1 ] = ∑ i = 1 n i × ( s s [ n ] − s s [ i − 1 ] ) − ( n − i + 1 ) × s s [ i − 1 ] \sum_{i=1}^n \sum_{l=1}^i \sum_{r=i}^n \sum_{k=l}^ra_k = \sum_{i=1}^n \sum_{l=1}^i \sum_{r=i}^n s[r]-s[l-1] \\ \sum_{i=1}^n \sum_{l=1}^iss[n]-ss[i-1]-\sum_{i=1}^n \sum_{l=1}^i(n-i+1)\times s[l-1] \\ = \sum_{i=1}^n i \times (ss[n]-ss[i-1])-\sum_{i=1}^n (n-i+1) \times ss[i-1] \\ = \sum_{i=1}^n i \times(ss[n]-ss[i-1])-(n-i+1)\times ss[i-1] i=1∑nl=1∑ir=i∑nk=l∑rak=i=1∑nl=1∑ir=i∑ns[r]−s[l−1]i=1∑nl=1∑iss[n]−ss[i−1]−i=1∑nl=1∑i(n−i+1)×s[l−1]=i=1∑ni×(ss[n]−ss[i−1])−i=1∑n(n−i+1)×ss[i−1]=i=1∑ni×(ss[n]−ss[i−1])−(n−i+1)×ss[i−1] - 原式变形到最后只与 i i i 有关,对数列扫一遍即可,复杂度 O ( T × N ) O(T\times N) O(T×N)
自我强调一下:取模运算涉及到减法,一定要取模防止负数!!具体细节详见code,(ans % mod + mod) % mod
加模再模(要不然WA爆一脸
Solution1:
void solve() {
int n; cin >> n;
vector<int> a(n+1);
vector<ll> s(n+1),ss(n+1);
for(int i=1;i<=n;i++) {
cin >> a[i];
s[i] = (s[i-1] + a[i]) % mod;
ss[i] = (ss[i-1] + s[i]) % mod;
}
ll ans = 0;
for(int i=1;i<=n;i++) {
ans = (ans%mod + (i*(ss[n]-ss[i-1]) - (n-i+1)*ss[i-1])%mod)%mod;
}
cout << (ans%mod+mod)%mod << endl; // 取模防止负数!!!!!
}
Analysis2:
- 我偏要打表!!!如下:
姿势优美
map<int,int> mp;
for(int n=1;n<=15;n++) {
mp.clear();
for(int i=1;i<=n;i++) {
for(int l=1;l<=i;l++) {
for(int r=i;r<=n;r++) {
for(int k=l;k<=r;k++) mp[k] += 1;
}
}
}
}
for(int i=1;i<=n;i++) cout << mp[i] << " \n"[i==n];
cout << endl;
- 结果如下:
n=1 : 1
n=2 : 3 3
n=3 : 6 8 6
n=4 : 10 15 15 10
n=5 : 15 24 27 24 15
n=6 : 21 35 42 42 35 21
n=7 : 28 48 60 64 60 48 28
n=8 : 36 63 81 90 90 81 63 36
n=9 : 45 80 105 120 125 120 105 80 45
n=10: 55 99 132 154 165 165 154 132 99 55
n=11: 66 120 162 192 210 216 210 192 162 120 66
n=12: 78 143 195 234 260 273 273 260 234 195 143 78
n=13: 91 168 231 280 315 336 343 336 315 280 231 168 91
n=14: 105 195 270 330 375 405 420 420 405 375 330 270 195 105
n=15: 120 224 312 384 440 480 504 512 504 480 440 384 312 224 120
- 根据数学直觉,发现每一列的两项之差均为等差数列,第1列的公差为1,第2列的公差为2,···,第p列的公差为p;
- 下面就是高中知识,经典的数列递推,利用累加法,便可求出每一列的通项,其中每一列的首项只与
p
p
p 有关,注意一下不同的n对应的每一列组成的新数列中取的下标随着
p
p
p 变动,本代码中用
k=n-i+1
来控制。耐心运算找规律去叭!(高中数列题 //确信)
Solution2:
// 求解每个下标对应的贡献次数
ll f(int k,int p) {
ll a1 = p*(p+1)/2;
ll b1 = ((p+5)*(p-2)/2+5);
ll ans = (a1 + (k-1)*b1 + (k-1)*(k-2)/2*p) % mod;
return (ans%mod+mod)%mod;
}
void solve() {
int n; cin >> n;
ll ans = 0;
for(int i=1;i<=n;i++) {
cin >> a[i];
ans = (ans % mod + a[i] % mod * f(n-i+1,i) % mod) % mod;
}
cout << (ans%mod+mod)%mod << endl;
}