分块、树状数组

题意:

因为上次在月月面前丢人了,所以华华决定开始学信息学。十分钟后,他就开始学树状数组了。这是一道树状数组的入门题:
图片说明
华华很快就学会了树状数组并通过了这道题。月月也很喜欢树状数组,于是给华华出了一道进阶题:
给定一个长度为N的序列A,所有元素初值为0。接下来有M次操作或询问:
操作:输入格式:1 D K,对于所有满足i≡0(modD)的i,将加上K。
询问:输入格式:2 L R,询问区间和,即sum(L,R)
华华是个newbie,怎么可能会这样的题?不过你应该会吧。
输入描述:
第一行两个正整数N、M表示序列的长度和操作询问的总次数。
接下来M行每行三个正整数,表示一个操作或询问。
输出描述:
对于每个询问,输出一个非负整数表示答案。

分析:

这题让我接触到了分块的思想,虽然很简单却很好。该说不愧是雨神挑选出来的题吗。似乎在多校第一场有一道题就有分块的思想,好像是分度数大的节点,和度数小的节点。当时就没做出来。现在看看这道题真的很有启发意义。

我们倘若直接用树状数组加的话,如果D过小比如1那么我们足足要加n次,时间复杂度爆炸。
那如果我们把每次要相加的记录下来lazy[D]+=K那么在我们计算和时

for (int i=1;i<=n;i++)
    ans+=(b/i - (a-1)/i)=lazy[i]

同样时间复杂度爆炸。

我们发现对于D过小时树状数组吃不消,D过大时lazy[]吃不消
那么分块的想法就出来了。
对于D<=sqrt(n)时我们使用lazy[]记录一下
对于D>sqrt(n)时我们用树状数组直接加上
在最后计算和时我们先用树状数组计算一下,然后再用lazy

for (int i=1;i<=sqrt(n);i++)
    ans+=(b/i - (a-1)/i)=lazy[i]

如此操作时间复杂度便大大减小了!!!!!!!简单而神奇。

代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int max_n = 1e5 + 100;
const int max_sq = 1e3;
int n, m;
ll BIT[max_n];
ll sq[max_sq];

void renew(int id,int val) {
    for (;id <= n;id += (id & -id))BIT[id] += val;
}

ll quiry(int id) {
    if (id == 0)return 0;
    ll res = 0;
    for (;id;id -= (id & -id))res += BIT[id];
    return res;
}

int main() {
    ios::sync_with_stdio(0);
    cin >> n >> m;
    for (int i = 1;i <= m;i++) {
        int type;int x, y;
        cin >> type >> x >> y;
        if (type == 1) {
            if (x <= sqrt(n))sq[x] += y;
            else {
                for (int i = 1;i * x <= n;i++)
                    renew(i * x, y);
            }
        }
        else {
            ll ans = quiry(y) - quiry(x - 1);
            for (int i = 1;i <= sqrt(n);i++)
                ans += (y / i - (x - 1) / i) * sq[i];
            cout << ans << endl;
        }
    }
}