博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
数列分块入门
阅读量:5321 次
发布时间:2019-06-14

本文共 4942 字,大约阅读时间需要 16 分钟。

这个人讲的特别好:

分块是一种暴力数据结构,可以处理区间操作等问题。

分块,就是分很多块来进行处理,这样查询时可以直接调用整块信息+暴力查询左右两端信息,将线性的枚举优化。

 

要分块,首先要确定块的大小。

一般来说,块的大小都为√n。这样可以保证整个序列被分成√n块,查询的时间复杂度可以达到平均。

分块算法中需要记录的信息有:

int sum[maxn]; //一个块的和int l[maxn]; //一个块的左端点(起点)int r[maxn]; //一个块的右端点(终点)int w[maxn]; //原数组int bel[maxn]; //第x个元素属于第bel[i]个块int siz; //块的大小,一般siz = sqrt(n)就可以了

初始分块

void build(int x){    for(int i = 1;i <= n;i++)        bel[i] = (i-1)/siz+1;    for(int i = 1;i <= bel[x];i++){        l[i] = (i-1)*siz+1;        r[i] = min(i*siz,x);        sum[i] = 0;    }}

区间修改

  先修改左端的不完整块,如果右边还有不完整块就再修改右边,最后修改整块的

void add(int a,int b,int c){    for(int i = a;i <= min(bel[a]*siz,b);i++)        v[i] += c;    if(bel[a] != bel[b])        for(int i = (bel[b]-1)*siz+1;i <= b;i++)            v[i] += c;    for(int i = bel[a]+1;i <= bel[b]-1;i++)        lazy[i] += c;}

  如果是区间乘法,需要每次暴力修改两端时先把lazy标记清除掉,

  修改整块的lazy标记时,默认乘法优先,所以需要每次把加法的lazy标记也乘以要修改的数。

  注意乘法的lazy标记要初始为1而不是0。

void reset(int x){    for(int i = l[x];i <= r[x];i++)        w[i] = (w[i]*mlaz[x] + alaz[x]) %mod;    alaz[x] = 0;    mlaz[x] = 1;}void multiply(int a,int b,int c){    reset(bel[a]);    for(int i = a;i <= min(r[bel[a]],b);i++)        w[i] *= c;    if(bel[a] != bel[b]){        reset(bel[b]);        for(int i = l[bel[b]];i <= b;i++)            w[i] *= c;    }    for(int i = bel[a]+1;i <= bel[b]-1;i++){        mlaz[i] *= c;        alaz[i] *= c;    }}
  区间修改(乘法)  

 区间查询

  其实一般lazy*siz就行了,但是r-l+1可以处理最后的完整块不足siz之类的情况(不过究竟会不会出现呢...)

int query(int a,int b){    int ans = 0;    for(int i = a;i <= min(r[bel[a]],b);i++)        ans += w[i] + lazy[bel[a]];    if(bel[a] != bel[b])        for(int i = l[bel[b]];i <= b;i++)            ans += w[i] + lazy[bel[b]];    for(int i = bel[a]+1;i <= bel[b]-1;i++)        ans += sum[i] + (r[bel[i]]-l[bel[i]]+1)*lazy[i];    return ans;

拓展

为什么要用分块?因为树状数组和线段树不好写

当树状数组和线段树不能直接套板子,需要比较大的改动,而分块看起来比较好膜改而且大概可以解决时

比如——

1.给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值 x 的前驱(比其小的最大元素)。

借用hzwer说的 数列简单分块问题实际上有三项东西要我们思考:对于每次区间操作:

  • 不完整的块的O(√n)个元素怎么处理?
  • O(√n)个 整块怎么处理?
  • 要预处理什么信息(复杂度不能超过后面的操作)?

对于这道题,要求每个块内的数据是有序的,可以考虑用一个vector来维护

修改函数、lazy标记的用法不变,

在初始分块和修改两端时需要重置,即把这个块内的数据重新排序。

vector
ve[maxn];void reset(int x) { ve[x].clear(); for (int i = l[x]; i <= r[x]; i++) ve[x].push_back(w[i]); sort(ve[x].begin(), ve[x].end());}int query(int a, int b, int c) { int ans = -1; for (int i = a; i <= min(r[bel[a]], b); i++) { int now = lazy[bel[a]] + w[i]; if (now < c) ans = max(ans, now); } if (bel[a] != bel[b]) for (int i = l[bel[b]]; i <= b; i++) { int now = lazy[bel[b]] + w[i]; if (now < c) ans = max(ans, now); } for (int i = bel[a] + 1; i <= bel[b] - 1; i++) { int d = c - lazy[i]; vector
::iterator it = lower_bound(ve[i].begin(), ve[i].end(), d) - 1; if (*it < d && it >= ve[i].begin() && it <= ve[i].end()) ans = max(ans, *it + lazy[i]); } return ans;}
    

 

2. 给出一个长为 n 的数列,以及 n 个操作,操作涉及单点询问(第x个数),单点插入(第x个数前)

每次将一个元素插入到某个元素的前面。有了前一道题的经验,很容易想到用vector存块。

但是这样要膜改的东西就比较多了,因为每个vector的长度都是不同的。

查询时,无法直接确定它在哪一个块内,需要每次调用当前块的size。

插入时,首先查询插入的前一个元素在哪一个块内,再把这个元素插入到对应的块里。

但是当某一个块过大时,需要的暴力操作就会过多,导致时间复杂度爆炸。

为了避免这种情况,需要引入一个操作:重新分块(重构)

√n次插入后,重新把数列平均分一下块,重构需要的复杂度为O(n),重构的次数为√n,复杂度没有问题,而且保证了每个块的大小相对均衡。

当然,也可以当某个块过大时重构,或者只把这个块分成两半。

struct node {    int bel, num;};node query(int x) {    node ans;    int now = 1;    while (x > ve[now].size()) x -= ve[now++].size();    ans.bel = now;    ans.num = x - 1;    return ans;}void rebuild() {    int cnt = 0;    int q[maxn];    for (int i = 1; i <= tot; i++) {        for (int j = 0; j < ve[i].size(); j++) q[++cnt] = ve[i][j];        ve[i].clear();    }    int new_siz = sqrt(cnt);    for (int i = 1; i <= cnt; i++) ve[(i - 1) / new_siz + 1].push_back(q[i]);    tot = (cnt - 1) / new_siz + 1;}void insert(int a, int b) {    node last = query(a);    ve[last.bel].insert(ve[last.bel].begin() + last.num, b);    if (ve[last.bel].size() > 2 * siz)        rebuild();}
    

 

3.给出一个长为 n的数列​,以及 n 个操作,操作涉及区间开方,区间求和。

 ん?(察觉)

开方不能对区间直接操作,但是暴力算法的时间复杂度显然不够。

通过查看题解思考可以发现,由于操作只有下开方,几次操作后,一个块内的数字会全部变为0或1,这一块的操作就可以跳过了。

开一个bool数组记录这一块是否还有不是0或1的数字,否则暴力。其他函数不变。

void radic(int a,int b){    if(a > b)swap(a,b);    for(int i = a;i <= min(r[bel[a]],b);i++){        if(flag[bel[a]])break;        sum[bel[a]] -= w[i];        w[i] = sqrt(w[i]);        sum[bel[a]] += w[i];    }    if(bel[a] != bel[b])        for(int i = l[bel[b]];i <= b;i++){            if(flag[bel[b]])break;            sum[bel[b]] -= w[i];            w[i] = sqrt(w[i]);            sum[bel[b]] += w[i];        }    for(int i = bel[a]+1;i <= bel[b]-1;i++){        if(flag[i])continue;        flag[i] = true;        sum[i] = 0;        for(int j = l[i];j <= r[i];j++){            w[j] = sqrt(w[j]);            sum[i] += w[j];            if(w[j] > 1)flag[i] = false;        }    }}
    

 

 

最后一个我还不会写,QAQ

转载于:https://www.cnblogs.com/mogeko/p/10429391.html

你可能感兴趣的文章
面对问题,如何去分析?(日报问题)
查看>>
Java多线程基础(一)
查看>>
SQL Server中利用正则表达式替换字符串
查看>>
初始面向对象
查看>>
leetcode Letter Combinations of a Phone Number
查看>>
Unity 5.4 测试版本新特性---因吹丝停
查看>>
7.5 文件操作
查看>>
MyEclipse中将普通Java项目convert(转化)为Maven项目
查看>>
node js 安装.node-gyp/8.9.4 权限 无法访问
查看>>
windows基本命令
查看>>
VMware中CentOS设置静态IP
查看>>
[poj1006]Biorhythms
查看>>
Hyper-V虚拟机上安装一个图形界面的Linux系统
查看>>
Hover功能
查看>>
js千分位处理
查看>>
Mac---------三指拖移
查看>>
字符串类型的相互转换
查看>>
HTTP状态码
查看>>
iOS如何过滤掉文本中特殊字符
查看>>
基础学习:C#中float的取值范围和精度
查看>>