Codeforces Round 920 (Div. 3) F. Sum of Progression 根号分治

题目链接

题目大意

        给你一个由 n 个数字组成的数组 a 。还有 q 个形式为 s,d,k 的查询。对于每个查询 q ,求元素 a [ s ]+ a [ s + d ] * 2 +⋯+ a [ s + d * ( k − 1 )] * k 的和。换句话说,对于每个查询,都需要求出数组中从 s 开始的 k 个元素的和,步长为 d ,乘以所得序列中元素的序号。

1≤ n ≤10^5,   1≤ q ≤2*10^5 ,    −10^8 ≤ a[1],...,a[n] ≤ 10^8,   1 ≤ s, d, k ≤ n ,   s+d⋅(k−1)≤n

思路

        由 s+d⋅(k−1)≤n知,d和k的数据范围相互影响,可以考虑根号分治。令m=sqrt(n), 当d>=m时,最多由n/d个数相加,即最多有sqrt(n)个数,时间复杂度为O(n\sqrt{n}),可暴力求解。

        当d较小时,观察a [ s ]+ a [ s + d ] * 2 +⋯+ a [ s + d * ( k − 1 )] * k发现,这个式子相当于初始为a[ s ],间隔为d的后缀和,而系数可通过后缀和套后缀和得到。

用sum[ i ] [ j ]表示为初始为a[ i ],间隔为j的后缀和,则sum[ i ][ j ]=sum[ i + j ][ j ] +  a[ i ];

再对sum[ i ] [ j ]求后缀和,用ssum [ i ] [ j ]表示初始为sum [ i ] [ j ],间隔为 j的后缀和,则ssum [ i ] [ j ]=ssum [ i +j ] [ j ]+sum [ i ] [ j ]。

则所求的式子则可通过 ssum [ s ] [ d ] - ssum [ s+d*k ] [ d ] - k*sum [ s+d*k ] [ d ]求得。

code

#include<bits/stdc++.h>
#define int long long
using namespace std;

//n最大位1e5,sqrt(n)最大位317.
//一定要加一个大于317的数,因为要求后缀和,会用到i+j,要防止越界
const int N = 1e5 + 350;

int sum[N][350], ssum[N][350], a[N];

void solve() {
	int n, q;
	cin >> n >> q;
	int m = sqrt(n);

	for (int i = 1; i <= n; ++i)
		cin >> a[i];

	for (int i = n; i >= 1; --i)
		for (int j = 1; j <= m; ++j)
			sum[i][j] = sum[i + j][j] + a[i];

	for (int i = n; i >= 1; --i)
		for (int j = 1; j <= m; ++j)
			ssum[i][j] = ssum[i + j][j] + sum[i][j];

	while (q--) {
		int s, d, k;
		cin >> s >> d >> k;
		if (d > m) {
			int ans = 0;
			for (int i = 1; i <= k; ++i) {
				ans += a[s + d * (i - 1)] * i;
			}
			cout << ans << " ";
		}
		else {
			int ans = ssum[s][d] - ssum[s + d * k][d] - k * sum[s + d * k][d];
			cout << ans << " ";
		}
	}
	cout << '\n';

	//有多组检测用例,要重置
	for (int i = n; i >= 1; --i)
		for (int j = 1; j <= m; ++j)
			sum[i][j] = ssum[i][j] = 0;
}
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int t = 1;
	cin >> t;
	while (t--) solve();
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值