分块是一种通用的暴力算法,可以用暴力的思想维护一些值,但复杂度却不是很高
把区间每
n−−√
n
个分成一个块,最后不足的也算一个块
分块的思想大体是“大段维护,局部朴素”
对于小区间朴素修改,对于大区间结合懒标记进行维护
因为可能询问和修改的区间十分大,l,r之间会有许多块,这个时候把l和r不足一整个块的部分直接暴力(朴素)遍历一遍,对于那些一整个块都被包含在l,r之间的可以进行一些维护,比遍历一遍要快很多
代码虽然很长,但大多都是重复的
例:用分块实现区间修改及查询
先来个简单点的
区间和
洛谷 P3372 【模板】线段树 1
因为习惯问题写太长了。。。
因为只有一种标记,所以直接维护块的和(一整块先打上标记不管),朴素更新零散点的值,同时仍然要维护块的和
询问时注意下传标记,就是。。。懒标记的思想是用的时候再更新,所以若一个点有所在的块有标记,则说明需要更新。而且朴素更新的那些点也更新了其块的大小,再加上懒标记一直攒着的就是答案
因为只有一种标记所以当成一个“增量”放那也行,就这道题来说不需要实时更新每个点的值,因为块的大小的更新可以不考虑每个点是多少,直接根据区间长度上标记就行
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define debug(x) cerr << #x << "=" << x << endl;
const int MAXN = 100000 + 10;
int n,m,p,a[MAXN],belong[MAXN],num;
long long b[MAXN];
struct blockk{
int l,r,sum;
long long add,mul;
}block[MAXN];
void build() {
int siz = sqrt(n);
num = n / siz;
if(n % siz) num++;
for(int i=1; i<=num; i++) {
block[i].l = (i-1)*siz + 1, block[i].r = i*siz;
block[i].mul = 1;
}
block[num].r = n;
for(int i=1; i<=n; i++) {
belong[i] = (i-1) / siz + 1;
}
}
void addition(int l, int r, int k) {
int p = belong[l], q = belong[r];
if(p == q) {
for(int i=l; i<=r; i++) {
a[i] += k;
}
block[p].sum += (r-l+1) * k;
} else {
for(int i=p+1; i<=q-1; i++) {
block[i].add += k;
}
for(int i=l; i<=block[p].r; i++) {
a[i] += k;
}
block[p].sum += (block[p].r - l + 1) * k;
for(int i=block[q].l; i<=r; i++) {
a[i] += k;
}
block[q].sum += (r - block[q].l + 1) * k;
}
}
long long ask(int l, int r) {
long long sum = 0;
int p = belong[l], q = belong[r];
if(p == q) {
for(int i=l; i<=r; i++) {
sum += a[i];
}
sum += (r-l+1) * block[p].add;
} else {
for(int i=p+1; i<=q-1; i++) {
sum += block[i].sum + (block[i].r-block[i].l+1) * block[i].add;
}
for(int i=l; i<=block[p].r; i++) {
sum += a[i];
}
sum += (block[p].r - l + 1) * block[p].add;
for(int i=block[q].l; i<=r; i++) {
sum += a[i];
}
sum += (r-block[q].l+1) * block[q].add;
}
return sum;
}
int main() {
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++) {
scanf("%d", &a[i]);
}
build();
for(int i=1; i<=n; i++) {
block[belong[i]].sum += a[i];
}
for(int i=1; i<=m; i++) {
int cmd,x,y,k;
scanf("%d", &cmd);
if(cmd == 1) {
scanf("%d%d%d", &x, &y, &k);
addition(x,y,k);
} else if(cmd == 2) {
scanf("%d%d", &x, &y);
printf("%lld\n", ask(x,y));
}
}
return 0;
}
区间乘法,区间和
这道题就有些不一样了,因为块的大小现在要关系到乘法了,乘法就要考虑每个点本身是什么,不像加法按区间长度来就行,两种标记混在一起乱搞的话就会失去时效性
所以上一道题可以随便写的话(我并没有实时更新块的和,而是拖到了询问时才更新),这一道题就要注意一些问题,块的和需要是最新值,这样我做乘法和加法才不会混。
举个例子,我对一个块先加a再乘c,我怎么维护块的和?
设某个块的和为x,现在和为x+c,再乘a,现在和是(x+c)* a
乘法后相对于之前增加了(x+c)* (a-1)
但是我要是直接打个标记就跑了。。。像上一题那样,假设我先处理乘法标记再处理加法标记,那么就错了吧。。。所以若块的和总是最新的,每次操作直接做就行
总之我的理解是:块的和必须是最新的,不能说留着标记去更新块的和,因为标记之间的关系难以处理,不如直接把块的和每次都处理好
但是仍然要记下标记,因为我只更新了块的和,并没有更新单点的值,这样在朴素更新的时候又会出问题了。。。所以在朴素更新之前下传标记就好了
另外,乘法标记对之前添加的加法标记也有作用,要注意更新加法标记
综上,更新和询问时,朴素更新之前下传一波(零散点所在块的)标记,让单点值最新,更新一波零碎单点所在的块,此时块的和最新,然后对好多个一整块的和进行更新,这些块目前也是最新的,然后对这些块打上标记,用于块里面的点未来的朴素更新
然而这样还是不能过这题,因为分块毕竟是暴力算法,得卡常,但是我不想卡了。。。
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define debug(x) cerr << #x << "=" << x << endl;
const int MAXN = 100000 + 10;
long long mod,a[MAXN],belong[MAXN],num;
int n,m;
struct blockk{
long long l,r,sum;
long long add,mul;
}block[MAXN];
inline void reset(int pos) {
for(int i=block[pos].l; i<=block[pos].r; i++) {
a[i] = (a[i] * block[pos].mul + block[pos].add) % mod;
}
block[pos].mul = 1;
block[pos].add = 0;
}
void build() {
int siz = sqrt(n);
num = n / siz;
if(n % siz) num++;
for(int i=1; i<=num; i++) {
block[i].l = (i-1)*siz + 1, block[i].r = i*siz;
block[i].mul = 1;
}
block[num].r = n;
for(int i=1; i<=n; i++) {
belong[i] = (i-1) / siz + 1;
block[belong[i]].sum += a[i];
block[belong[i]].sum %= mod;
}
}
void multi(int l, int r, int k) {
int p = belong[l], q = belong[r];
reset(p);
if(p == q) {
for(int i=l; i<=r; i++) {
block[p].sum += a[i] * (k-1);
a[i] = (a[i] * k) % mod;
}
block[p].sum %= mod;
} else {
reset(q);
for(int i=l; i<=block[p].r; i++) {
block[p].sum += a[i] * (k-1);
a[i] = (a[i] * k) % mod;
}
block[p].sum %= mod;
for(int i=p+1; i<=q-1; i++) {
block[i].add *= k;//乘法对加法的作用
block[i].add %= mod;
block[i].mul *= k;
block[i].mul %= mod;
block[i].sum *= k;
block[i].sum %= mod;
}
for(int i=block[q].l; i<=r; i++) {
block[q].sum += a[i] * (k-1);
a[i] = (a[i] * k) % mod;
}
block[q].sum %= mod;
}
}
void addition(int l, int r, int k) {
int p = belong[l], q = belong[r];
reset(p);
if(p == q) {
for(int i=l; i<=r; i++) {
a[i] = (a[i] + k) % mod;
}
block[p].sum = (block[p].sum + (r - l + 1) * k) % mod;
} else {
reset(q);
for(int i=l; i<=block[p].r; i++) {
a[i] = (a[i] + k) % mod;
}
block[p].sum = (block[p].sum + (block[p].r - l + 1) * k) % mod;
for(int i=p+1; i<=q-1; i++) {
block[i].add += k;
block[i].add %= mod;
block[i].sum += (block[i].r - block[i].l + 1) * k;
block[i].sum %= mod;
}
for(int i=block[q].l; i<=r; i++) {
a[i] = (a[i] + k) % mod;
}
block[q].sum = (block[q].sum + (r - block[q].l + 1) * k) % mod;
}
}
long long calc(int l, int r) {
long long sum = 0;
int p = belong[l], q = belong[r];
reset(p);
if(p == q) {
for(int i=l; i<=r; i++) {
sum = (sum + a[i]) % mod;
}
} else {
reset(q);
for(int i=l; i<=block[p].r; i++) {
sum = (sum + a[i]) % mod;
}
for(int i=p+1; i<=q-1; i++) {
sum = (sum + block[i].sum) % mod;
}
for(int i=block[q].l; i<=r; i++) {
sum = (sum + a[i]) % mod;
}
}
return sum;
}
int main() {
scanf("%d%d%lld", &n, &m, &mod);
for(int i=1; i<=n; i++) {
scanf("%lld", &a[i]);
}
build();
for(int i=1; i<=m; i++) {
int cmd,x,y;
long long k;
scanf("%d", &cmd);
if(cmd == 1) {
scanf("%d%d%lld", &x, &y, &k);
multi(x,y,k);
} else if(cmd == 2) {
scanf("%d%d%lld", &x, &y, &k);
addition(x,y,k);
} else {
scanf("%d%d", &x, &y);
printf("%lld\n", calc(x, y));
}
}
return 0;
}