分块小记

优雅的暴力

前言

如果对于一个序列,如果要求和。一个个地处理需要 \(O(n)\) 但是假如我们将序列预处理好,再算就是 \(O(1)\) 的。但要是还要修改呢?

分块就是一个折中的办法.可以做一些线段树没法做的事情.

区间加区间和

下面讲解区间加区间和的思路.

假设块长度为 \(len\),数量为 \(num=n/len\)。那么我们可以记录数组 \(tag_k\)。表示第 \(k\) 个块的标记,还有 \(s_k\) 表示第 \(k\) 个块的和。

如果现在要给 \([l,r]\) 都加上 \(p\).

如果区间覆盖了整个块,直接\(tag_x \leftarrow tag_x + p,s_x \leftarrow tag_x \times len\) (这个 \(len\) 可能受到边界的影响) 就可以了。
如果不是,直接在原数列上进行修改,即暴力。

查询的时候也是一样的.
如果区间覆盖了整个块,直接 \(ans\leftarrow ans+s_x\).
如果不是,直接暴力求和.


代码(luogu P3372)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5+10;
int a[N],id[N];
int len;
int s[N], tag[N];
#define l(i) ((i)*(len-1)+1)
#define r(i) (min((i)*len,n))
void bupd(int l,int r,int c) {
	for(int i=l;i<=r;i++) {
		a[i]+=c,s[id[i]]+=c;
	}
}
void upd(int l,int r,int c) {
    if(id[l] == id[r]) {
        bupd(l,r,c);
        return ;
    }
    bupd(l,r(id[l])),bupd(l(id[r]),r);
    for(int i=id[l]+1;i<id[r];i++) {
        tag[i] += c, s[i] += len*c;
    }
}
void bqry(int l,int r,int mod) {
	int p = 0;
	for(int i=l;i<=r;i++) {
		(p += a[i] + tag[id[i]]) %= mod;
	}
	return p;
}
int query(int l,int r,int mod) {
    int ans = 0;
    if(id[l] == id[r]) {
        return bqry(l,r,mod);
    }
    (ans += bqry(l,r[id[l]],mod) + bqry(l(id[r]),r,mod))%=mod;
    for(int i=id[l]+1;i<id[r];i++) {
        (ans+=s[i])%=mod;
    }
    return ans%mod;
}
signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n; cin>>n;
    len = sqrt(n);
    for(int i=1;i<=n;i++) {
        cin>>a[i];
        id[i] = (i-1)/len + 1;
        s[id[i]] += a[i];
    }
    while(n--) {
        int o,l,r,k;
        cin>>o>>l>>r>>k;
        if(o==0) upd(l,r,k);
        else cout<<query(l,r,k+1)<<"\n";
    } 
    return 0;
}
posted @ 2024-03-18 13:43  cjrqwq  阅读(21)  评论(0)    收藏  举报