题目大意
一个长度为 500000 (5*10**5),起始下标为1,初始值为 0 的数组,进行 q 次询问,每次询问有两张情况:
1.下标为x的数加上y
2.查询下标 i 满足 (i%x)== y 的数组元素的和。
题目分析
对于这类区间查询,可以先试试暴力,对于第一类查询,直接模拟,在数组的对应下标位置加 y ;对于第二类查询,在[1,500000]的区间内暴力枚举每一个(i%x)== y 的值,然后输出它们的和。但显然,会导致超时,得想办法优化。
一.合理剪枝
根据题意可知,数组的初始值全为0。也就是说:未被修改的值对答案的贡献必然为0。那么,我们记录已经修改过的值的下标的最大值 max ,将第二类查询的区间优化为[1,max]。可以发现,能多过几个样例,但是还是TLE,还得再优化。
二.合理打表
在剪枝的前提下,第一类查询的时间复杂度为O(1);第二类查询的时间复杂度为O(log max)。但是,当查询的除数 x 为1时,第二类查询的时间复杂度会退化为O(max),这是不能接受的。于是,我们可以想到,可以用一个变量记录 x = 1,即整个数组的和,那么,当 x = 1的时候,可以直接输出,时间复杂度进化为O(1)。可以发现,样例过了16个,但还是TLE,还得优化。
顺着这个思路,我们可以进一步思考:在第二次查询中,当 x 较小的时候,遍历[1,max]中的所有符合条件的数,时间复杂度是比较高的。我们可以将一部分 x 较小的情况,在第一次查询中就维护好它们的值,在第二次查询时直接输出。这样,虽然增大了第一类查询的时间复杂度,但是可以降低第二类的时间复杂度,进而在总体上达到一个相对理想的状态。
最后,选择前 i 个数(i 为超参数,即可以人为定义的参数,根据个人喜好即可,目前测试的是,在[5,1000]的区间内,是可以AC本题的。在不完全统计的情况下,i = 10 的时候,用时561ms,最优,相较于洛谷题解的分块做法,是快非常多的(喜,其实是参数设定的更加符合答案数据,理论上用sqrt(n)作为参数是更适用的)),在第一类查询的时候就对第二类查询时x = i 的情况进行记录,在第二类查询时,若已经记录,则直接输出,反之暴力遍历区间内所有可能结果,输出和,即可。
代码实现
#include <iostream>
using namespace std;
int t, x, y, q, a[500010] = {}, ans, sum = 0, mx = 0, x2[2] = {}, x3[3] = {}, x4[4] = {}, x5[5] = {};//给出取前5个数的做法,想多取可以开二维数组
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> q;
while (q--) {
cin >> t >> x >> y;
if (t == 1) {
a[x] += y;
mx = max(x, mx);//记录已修改的值的下标最大值
sum += y;//打表,记录x在[1,5]之间的各种情况的答案,下标为y的取值
x2[x % 2] += y;
x3[x % 3] += y;
x4[x % 4] += y;
x5[x % 5] += y;
}
else {
if (x == 1)cout << sum << '\n';//如果在已经记录的区间内,则直接输出
else if (x == 2)cout << x2[y] << '\n';
else if (x == 3)cout << x3[y] << '\n';
else if (x == 4)cout << x4[y] << '\n';
else if (x == 5)cout << x5[y] << "\n";
else {//不在,则暴力枚举区间内所有情况,输出和
ans = 0;
for (int i = 0; i * x + y <= mx; i++) {
ans += a[i * x + y];
}
cout << ans << "\n";
}
}
}
}
优化思路
在目前的情况下,可以通过卡数据的方法Hack(我们也可以修改参数应对);或者,如果我们在第一类查询记录的数,在第二类查询中都没有出现,不就成小丑了吗(bushi)。有没有更加稳妥的方法呢?
有的兄弟,有的。
我们可以离线处理答案,记录第二类查询中 x 较小(那什么是较小呢?自己定义吧)的x值,然后在从上到下处理访问,将第二类查询中出现的 x 较小的情况进行记录,然后按照上述操作进行即可。
最后,如果有佬真的做了,欢迎在评论区讨论,还请不吝赐教()