分块 学习笔记

27 篇文章 0 订阅
5 篇文章 0 订阅

分块是一种通用的暴力算法,可以用暴力的思想维护一些值,但复杂度却不是很高
把区间每 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;
}

区间乘法,区间和

洛谷 P3373 【模板】线段树 2

这道题就有些不一样了,因为块的大小现在要关系到乘法了,乘法就要考虑每个点本身是什么,不像加法按区间长度来就行,两种标记混在一起乱搞的话就会失去时效性
所以上一道题可以随便写的话(我并没有实时更新块的和,而是拖到了询问时才更新),这一道题就要注意一些问题,块的和需要是最新值,这样我做乘法和加法才不会混

举个例子,我对一个块先加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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值