【题解】Journey 前缀和与二重前缀和


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=1nl=1ir=ink=lrak

Constrains:

  • 1 ≤ T ≤ 1 0 5 1\leq T \leq 10^5 1T105
  • 1 ≤ n ≤ 1 0 5 1\leq n \leq 10^5 1n105
  • 0 ≤ a i ≤ 1 0 9 0 \leq a_i \leq10^9 0ai109
  • ∑ n ≤ 2 × 1 0 5 \sum n \leq 2\times 10^5 n2×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=1nl=1ir=ink=lrak=i=1nl=1ir=ins[r]s[l1]i=1nl=1iss[n]ss[i1]i=1nl=1i(ni+1)×s[l1]=i=1ni×(ss[n]ss[i1])i=1n(ni+1)×ss[i1]=i=1ni×(ss[n]ss[i1])(ni+1)×ss[i1]
  • 原式变形到最后只与 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值