Bootstrap

AT_arc100_c [ARC100E] Or Plus Max 暴力 + 打表(附优化思路)

题目大意

一个长度为 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 较小的情况进行记录,然后按照上述操作进行即可。

最后,如果有佬真的做了,欢迎在评论区讨论,还请不吝赐教()

;