前言
早就看过黄学长这篇博客一直没有学习过,写牛客的第一场多校的时候有一道莫队的题,之后突然想起来这篇,所以学习了一下,不得不说这种分块儿思想,真是优美的暴力啊~下面我会上9道题(其实就是黄学长博客上面的题)来简述一下分块这种思想 。



给出一个长为 nn{n} 的数列,以及 nn{n} 个操作,操作涉及区间加法,单点查值。
思路:这道题可以用很多数据结构写,我们这里引入分块的思想,我们把这长度为n的序列分成m块,现有一个L,R区间进行加和操作,那么分为两种:对于块内,我们维护一个标记数组,记录的是他整个区间的加法操作,那么对于快外的两个区间的话,我们暴力的去修改,对于查询操作,就是当前节点的值加上他所在块儿的值。
那么讨论一下复杂度:
显然对于一次区间操作来说,最多每次操作会涉及n+mn+m{n+m}个整块儿,以及最多,2∗m2∗m{2*m}个分块操作,那么对于总的时间复杂度其实就是 O(n+m)O(n+m){O(n+m)},根据均值不等式的计算我们可以知道 当 m=(√n)m=(n)m = {\sqrt(n)} 的时候有最小值,所以我们分块的大小为n−−√n{\sqrt{n}}。
那么对于总的时间复杂度就是O(n∗n−−√)O(n∗n){O(n*{\sqrt{n}})};
上代码把:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 50000 + 10;
int a[maxn] , pos[maxn] , tar[maxn] , sz , n;
void update(int l,int r,int c)
{for(int i = l ; i <= min(pos[l]*sz ,r) ; i ++) a[i] += c; // 对于块外的元素(左区间)if(pos[l] != pos[r]) for(int i = (pos[r]-1)* sz +1; i <= r ; i++) a[i] += c; // 右区间for(int i = pos[l] + 1 ; i <= pos[r] - 1 ; i++) tar[i] += c; // 对于块内元素直接加一个标记
}
int main()
{scanf("%d",&n);sz = sqrt(n);for(int i = 1 ; i <= n ; i++){scanf("%d",&a[i]);pos[i] = (i-1)/sz+1;}int op,l,r,c;for(int i = 0 ; i < n ; i++){scanf("%d%d%d%d",&op,&l,&r,&c);if(op == 0){update(l,r,c);}if(op == 1) printf("%d\n",a[r] + tar[pos[r]]);}
}

2
给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的元素个数。
根据前面的一道题我们已经大致可以了解出分块时我们要考虑的东西了,就是:
1.对于整块元素我们怎样办?
2.对于块外元素怎样办?
第二个问题一般都非常简单,暴力查小于x的值的元素个数,那么对于第一个问题,我们应该怎样做呢?
仔细想想我们块内的元素顺序其实已经没有那种重要了,我们只需要知道这些元素在一个块儿内就好了,所以我们可以….排序+二分。对吧,对于快内元素我们排序二分一下就好了,对于区间加操作我么还是快外暴力加,加完之后对于一个n−−√n{\sqrt{n}}的块重新排序时间复杂度是n∗lognn∗log⁡n{n*\log_{}{n}},之后块内打标记,对于查询操作,快外暴力找,块内二分查,想一下时间复杂度,每次加法操作涉及区间O(n−−√n{\sqrt{n}}),对于查询,块外是O(n−−√)O(n){O({\sqrt{n})}} ,块内n−−√∗logn−−√n∗log⁡n{\sqrt{n}*\log_{}\sqrt{n}},总时间复杂度是
O(nlogn + n√nlog√n)
上代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 50000 + 10;
int v[maxn] , pos[maxn] , tar[maxn] , sz , n;
vector<int>V[maxn];
void reset(int x)
{V[pos[x]].clear();for(int i = (pos[x]-1) * sz + 1 ; i <= min(pos[x]*sz,n) ; i++) V[pos[x]].push_back(v[i]); sort(V[pos[x]].begin() , V[pos[x]].end());
}
void add(int l,int r,int add)
{for(int i = l ; i <= min(r,pos[l]*sz) ; i++) v[i] += add; reset(l);if(pos[l] != pos[r]){for(int i = (pos[r] - 1) * sz + 1; i <= r ; i++) v[i] += add;reset(r);}for(int i = pos[l] + 1 ; i <= pos[r] - 1 ; i++) tar[i] += add;
}
int query(int l,int r,int target)
{int ans = 0 ;for(int i = l ; i <= min(r,pos[l]*sz) ; i++) if(v[i] + tar[pos[l]]< target) ans++;if(pos[l] != pos[r]){for(int i = (pos[r] - 1) * sz + 1 ; i <= r ; i++) if(v[i] + tar[pos[r]] < target) ans ++;}for(int i = pos[l] + 1 ; i <= pos[r] - 1 ; i++){int x = target - tar[i];ans += lower_bound(V[i].begin() , V[i].end() , x) - V[i].begin();}return ans;
}
int main()
{scanf("%d",&n);sz = sqrt(n);for(int i = 1 ; i <= n ; i++){scanf("%d",&v[i]);pos[i] = (i-1)/sz+1;V[pos[i]].push_back(v[i]);}for(int i = 1 ; i <= pos[n] ; i++) sort(V[i].begin() , V[i].end());int op,l,r,c;for(int i = 1 ; i <= n ; i++){scanf("%d%d%d%d",&op,&l,&r,&c);if(op == 0) add(l,r,c);if(op == 1) printf("%d\n",query(l,r,c*c));}
}

3.
给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的前驱(比其小的最大元素)。
思路
和第二题有点像,改改就好了,有没有更简单的操作?
set。。每个块里维护一个set,之后在set里二分找
代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100000+10;
int v[maxn] , pos[maxn] , tar[maxn] , sz , n;
set<int>S[maxn];
void add(int l,int r,int add)
{for(int i = l ; i <= min(r,pos[l]*sz) ; i++){S[pos[l]].erase(v[i]);v[i] += add; S[pos[l]].insert(v[i]);}if(pos[l] != pos[r]){for(int i = (pos[r] - 1) * sz + 1; i <= r ; i++){S[pos[r]].erase(v[i]);v[i] += add;S[pos[r]].insert(v[i]);}}for(int i = pos[l] + 1 ; i <= pos[r] - 1 ; i++) tar[i] += add;
}
int query(int l,int r,int target)
{int ans = -1 ;for(int i = l ; i <= min(r,pos[l]*sz) ; i++) if(v[i] + tar[pos[l]] < target) ans = max(ans,v[i] + tar[pos[l]]); if(pos[l] != pos[r]){for(int i = (pos[r] - 1) * sz + 1 ; i <= r ; i++) if(v[i] + tar[pos[r]] < target) ans = max(ans,v[i] + tar[pos[r]]);}for(int i = pos[l] + 1 ; i <= pos[r] - 1 ; i++){int x = target - tar[i];auto it = S[i].lower_bound(x);if(it == S[i].begin()) continue;--it;ans = max(ans,*it + tar[i]);}return ans;
}
int main()
{scanf("%d",&n);sz = sqrt(n);for(int i = 1 ; i <= n ; i++){scanf("%d",&v[i]);pos[i] = (i-1)/sz+1;S[pos[i]].insert(v[i]);}int op,l,r,c;for(int i = 1 ; i <= n ; i++){scanf("%d%d%d%d",&op,&l,&r,&c);if(op == 0) add(l,r,c);if(op == 1) printf("%d\n",query(l,r,c));}
}

4.给出一个长为n的数列,以及n个操作,操作涉及区间加法,区间求和。
思路
块外怎么办,块内怎么办?
对于修改来说都已经很简单了,那么对于查询我们如何快速的查询区间的和呢?
我们需要另外开一个sum数组来记录区间的和,对于块外暴力修改的结果我们直接加到相对应的块的中,对于整个块的操作来说我们还是和原来一样记录他的标记,之后对于区间的求和我们块外就可以他的值加上他所在块儿内的值,块内我们就可以直接sum加上我们维护的tar数组。感觉这里说的不是很清晰,看了一眼黄学长写的

“ 这题的询问变成了区间上的询问,不完整的块还是暴力;而要想快速统计完整块的答案,需要维护每个块的元素和,先要预处理一下。
考虑区间修改操作,不完整的块直接改,顺便更新块的元素和;完整的块类似之前标记的做法,直接根据块的元素和所加的值计算元素和的增量。”
精髓呀
代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 50000 + 10;
long long v[maxn] ,  sum[maxn] , tar[maxn];
int n,sz,pos[maxn] ;
void update(int l,int r,int c)
{for(int i = l ; i <= min(pos[l] * sz , r) ; i++) v[i] += c,sum[pos[l]] += c;if(pos[l] != pos[r]){for(int i = (pos[r] -1 ) * sz + 1 ; i <= r ; i++) v[i] += c,sum[pos[r]] += c;}for(int i = pos[l] + 1 ; i <= pos[r] -1 ; i++) tar[i] += c;
}
long long query(int l,int r)
{long long ans = 0;for(int i = l ; i <= min(pos[l] * sz , r) ; i++) ans += v[i] + tar[pos[l]];if(pos[l] != pos[r]) for(int i = (pos[r] -1)*sz + 1 ; i <= r; i++){ans += v[i] + tar[pos[r]];}for(int i = pos[l] + 1 ; i <= pos[r] - 1 ; i++) ans += sum[i] + tar[i] * sz;return ans;
}
int main()
{scanf("%d",&n);sz = sqrt(n);for(int i = 1 ; i <= n ; i++){scanf("%lld",&v[i]);pos[i] = (i-1)/sz+1;sum[pos[i]] += v[i];}int op,l,r,c;for(int i = 0 ; i < n ;i++){scanf("%d%d%d%d",&op,&l,&r,&c);if(op == 0) update(l,r,c);if(op == 1) printf("%lld\n",query(l,r) % (c+1));        }
}

5
给出一个长为n的数列,以及n个操作,操作涉及区间开方,区间求和。
思路
这道题对应的应该是一道我写过的线段树的题,仔细想想一个int类型值每次向下取整最多4次就会变成0(???),那么我们暴力的去修改暴力的去求和就好了,之后在块儿上面打标记,倘若这个块儿里的值全为0了,那么我们就不去修改他
代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 50000 + 10;
int pos[maxn] , v[maxn] , sz , sum[maxn] , tar[maxn] ,flag[maxn];
void slove_sqrt(int x)
{if(flag[x]) return ;flag[x] = 1 ;sum[x] = 0;for(int i = (x - 1) * sz + 1; i <= x*sz ;i ++){v[i] = sqrt(v[i]);sum[x] += v[i];if(v[i] > 1) flag[x] = 0;}
}
void update(int l,int r,int c)
{for(int i = l ; i <= min(pos[l]*sz , r) ; i++){sum[pos[l]] -= v[i];v[i] = sqrt(v[i]);sum[pos[l]] += v[i];}if(pos[l] != pos[r]){for(int i = (pos[r]-1) * sz + 1 ; i <= r ; i++){sum[pos[r]] -= v[i];v[i] = sqrt(v[i]);sum[pos[r]] += v[i];}}for(int i = pos[l] + 1 ; i <= pos[r] - 1 ; i++){slove_sqrt(i);}
}
int query(int l,int r)
{int ans = 0 ;for(int i = l ; i <= min(r,pos[l] * sz) ; i++) ans += v[i];if(pos[l] != pos[r]){for(int i = (pos[r]-1) * sz + 1 ; i <= r ; i++) ans+=v[i];}for(int i = pos[l]+ 1 ; i <= pos[r] - 1 ; i ++) ans += sum[i];return ans;
}
int main()
{int n;scanf("%d",&n);sz = sqrt(n);for(int i = 1 ; i <= n ; i++){scanf("%d",&v[i]);pos[i] = (i-1)/sz+1;sum[pos[i]] += v[i];}int op,l,r,c;for(int i = 0 ; i < n ; i++){scanf("%d%d%d%d",&op,&l,&r,&c);if(op == 0) update(l,r,c);if(op == 1)printf("%d\n",query(l,r));}
}

6
给出一个长为n的数列,以及n个操作,操作涉及单点插入,单点询问,数据随机生成。
思路
对于一个块儿他可以插入数据我们很显然要用vector,开一个二维的vector ,之后在找到相应位置的时候,插在这个块里,但是当某一个块儿中的数过多的时候我们的复杂度就会变得不稳定,于是就引入了重新分块的这一操作,简单来说就是将这些块重新分组
代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 50000 + 10;
int pos[maxn] , v[maxn] , sz , sum[maxn] ,flag[maxn] , m ,te[maxn];
vector<int>V[maxn];
pair<int ,int> query(int x)
{int P = 1;while(x > V[P].size()){x -= V[P].size();P++;}return make_pair(P,x-1);
}
void rebuild()
{int cnt = 1;for(int i = 1 ; i <= m ; i ++){for(int j = 0 ; j < V[i].size() ; j ++){te[cnt++] = V[i][j];}V[i].clear();}int pos2 = sqrt(cnt);for(int i = 1 ; i <= cnt ; i++){V[(i-1)/pos2+1].push_back(te[i]);}m = (cnt-1)/sz+1;
}
void insert(int l,int r)
{pair<int ,int > T = query(l);V[T.first].insert(V[T.first].begin()+T.second,r);if(V[T.first].size() > 20 * sz) rebuild();
}
int main()
{int n;scanf("%d",&n);sz = sqrt(n);for(int i = 1 ; i <= n ; i++){scanf("%d",&v[i]);V[(i-1)/sz+1].push_back(v[i]);}m = (n-1)/sz+1;int op,l,r,c;for(int i = 0 ; i < n ; i++){scanf("%d%d%d%d",&op,&l,&r,&c);if(op == 0) insert(l,r);if(op == 1) {pair<int,int> T = query(r);cout<<V[T.first][T.second]<<endl;}}
}

7.给出一个长为n的数列,以及n个操作,操作涉及区间乘法,区间加法,单点询问。
思路
还是老套路,块外怎么办,块内怎么办?
由于涉及加和乘操作,所以我们可以想到维护两个数组一个加法数组一个乘法数组(显然??),(3+7)∗3==3∗3+7∗3(3+7)∗3==3∗3+7∗3{(3+7)*3 == 3 * 3 + 7*3}对吧,那么每次涉及到乘法操作的时候就是他的加法数组也要乘上这个数字,考虑一下如果一个值的所在块已经有标记了,之后我们暴力去修改他会不会出问题?当然会了,所以这里我们要有一个线段树的类似push_down操作,当我们暴力去修改块儿元素的时候要把标记下放到我们的原数组里,之后标记清空就好了。
代码

#include <bits/stdc++.h>
const int maxn = 100000 + 10;
const int mod = 10007;
using namespace std;
int v[maxn],pos[maxn],sum[maxn],mtar[maxn],atar[maxn];
int sz , n;
void reset(int x)
{for(int i=(x-1)*sz+1;i<=min(n,x*sz);i++)v[i]=(v[i]*mtar[x]+atar[x])%mod;atar[x]=0;mtar[x]=1;
}
void update(int l,int r,int c,int op)
{reset(pos[l]);for(int i = l ; i <= min(r,pos[l]*sz) ; i++){if(op) v[i] *= c;else v[i] += c;;v[i]%=mod;}if(pos[l] != pos[r]){reset(pos[r]);for(int i = (pos[r] - 1 ) * sz + 1 ; i <= r ; i ++){if(op) v[i] *= c;else v[i] += c;v[i] %= mod;}}for(int i = pos[l] + 1 ; i <= pos[r] - 1 ; i++){if(op){atar[i] = (atar[i] * c) % mod;mtar[i] = (mtar[i] * c) % mod;}else atar[i] = (atar[i] + c) % mod;}
}
int main()
{scanf("%d",&n);sz = sqrt(n);for(int i = 1 ; i <= n ;i ++){scanf("%d",&v[i]);pos[i] = (i-1)/sz+1;sum[pos[i]] += v[i];mtar[i] = 1;}int op,a,b,c;for(int i = 0 ; i < n ; i++){scanf("%d%d%d%d",&op,&a,&b,&c);if(op == 2) printf("%d\n",((v[b] * mtar[pos[b]] + atar[pos[b]]) ) % mod);else update(a,b,c,op);}
}

8
给出一个长为n的数列,以及n个操作,操作涉及区间询问等于一个数c的元素,并将这个区间的所有元素改为c。
思路

黄学长的解析:区间修改没有什么难度,这题难在区间查询比较奇怪,因为权值种类比较多,似乎没有什么好的维护方法。

模拟一些数据可以发现,询问后一整段都会被修改,几次询问后数列可能只剩下几段不同的区间了。

我们思考这样一个暴力,还是分块,维护每个分块是否只有一种权值,区间操作的时候,对于同权值的一个块就O(1)统计答案,否则暴力统计答案,并修改标记,不完整的块也暴力。

这样看似最差情况每次都会耗费O(n)的时间,但其实可以这样分析:

假设初始序列都是同一个值,那么查询是O(√n),如果这时进行一个区间操作,它最多破坏首尾2个块的标记,所以只能使后面的询问至多多2个块的暴力时间,所以均摊每次操作复杂度还是O(√n)。

换句话说,要想让一个操作耗费O(n)的时间,要先花费√n个操作对数列进行修改。

初始序列不同值,经过类似分析后,就可以放心的暴力啦。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100000+10;
int sz,v[maxn] , pos[maxn] , n , l,r,c , tar[maxn];
void reset(int x)
{if(tar[x] == -1) return ;for(int i = (x-1)*sz+1 ; i <= min(n,x*sz) ; i++) v[i] = tar[x];tar[x] = -1;
}
int update(int l,int r,int c)
{int ans = 0 ;reset(pos[l]);for(int i = l ; i <= min(r,pos[l] * sz ) ; i++){if(v[i] != c) v[i] = c;else ans++;}if(pos[l] != pos[r]){reset(pos[r]);for(int i = (pos[r] - 1) * sz + 1 ; i <= r; i ++){if(v[i] != c) v[i] = c;else ans++;}}for(int i = pos[l] + 1 ; i <= pos[r] - 1 ; i++){if(tar[i] != -1){if(tar[i] == c) ans+=sz;else tar[i] = c;}else {for(int j = (i-1) * sz + 1 ; j <= min(n,i*sz) ;  j++){if(v[j] != c) v[j] =c;else ans++;}tar[i] = c;}}return ans;
}
int main()
{memset(tar,-1,sizeof(tar));scanf("%d",&n);sz = sqrt(n);for(int i = 1 ; i <= n ; i++){scanf("%d",&v[i]);pos[i] = (i-1)/sz+1;}for(int i = 0 ; i < n ; i++){scanf("%d%d%d",&l,&r,&c);printf("%d\n",update(l,r,c));}
}

跟着hzw学习数列分块相关推荐

  1. hzw的数列分块入门

    T1 数列区间修改,单点查询.线段树,树状数组都可.(代码抄的) 1 #include <bits/stdc++.h> 2 3 using namespace std; 4 5 typed ...

  2. 【分块入门】LOJ 数列分块入门 1 - 9 (学习更新……)

    dl题解 _「分块」数列分块入门1 – 9 by hzwer LOJ #6277. 数列分块入门 1 题意:给出一个长为n的数列,以及n个操作,操作涉及区间加法,单点查值. 时间限制:100ms 分块 ...

  3. #6277. 数列分块入门 1

    题目链接:https://loj.ac/problem/6277 学习博客:http://hzwer.com/8053.html #6277. 数列分块入门 1 内存限制:256 MiB时间限制:10 ...

  4. #6279. 数列分块入门 3(区间修改,查询权值前驱)

    #6279. 数列分块入门 3 这是使用hzwer建议的set写的分块代码,set自动排序,支持二分查找,但是常数较大,比我下面写的用vector实现的分块慢了三倍,空间大了10倍. #include ...

  5. LOJ 数列分块入门6

    LOJ 数列分块入门6 题目: 题目 题解: 我都不懂这题为什么要用分块... ... 直接vector就好了... 但是如果有区间修改的话就不行了.所以这题是启示我们也可以动态分块.具体就是每次插入 ...

  6. 算法笔记--数列分块

    黄老师的博客http://hzwer.com/8053.html 模板: const int N=1e5+5; int a[N],belong[N]/*属于哪个块*/,blo/*块的大小*/,bloc ...

  7. LOJ #6280. 数列分块入门 4-分块(区间加法、区间求和)

    #6280. 数列分块入门 4 内存限制:256 MiB时间限制:500 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: hzwer 提交提交记录统计测试数据讨论 题目描述 给出一个长为 ...

  8. 数列分块入门(套题)(loj6277,loj6278,loj6279,loj6280,loj6281,loj6282,loj6283,loj6284,loj6285)

    前言 zjoi考差了,码一些分块题缓解一下心情 壹 数列分块入门 1[loj6277] 题目大意:区间加,单点查 直接分块,区间加时完全覆盖的块打tag,边界块暴力重构 块大小设为n\sqrt nn​ ...

  9. 数列分块入门 (1 ~ 7)

    分块 6277. 数列分块入门 1 分块思想 我们把每m个元素分成一块,所以我们总共的块数就是n/mn / mn/m块,一般情况下我们取m=nm = \sqrt{n}m=n​.对于区间加操作,我们可以 ...

最新文章

  1. 商汤涨涨涨涨:上市4日股价累涨130%,市值一度3000亿港元
  2. 12层打败50层,ParNet 普林斯顿+英特尔:更深不一定更好
  3. mysql路径查找_如何在MySQL的具有文件路径的列中查找和替换?
  4. 什么是方向图乘积定理_初中数学竞赛试题——正多边形与托勒密定理
  5. 测试dali协议的软件,基于DALI协议的数字照明控制软件的研发
  6. Struts2笔记——struts常用标签
  7. 【ZOJ - 4019】Schrödinger's Knapsack (dp,背包,贪心,组内贪心组间dp)
  8. Mysql之数据库锁(表锁和行锁)详解
  9. 双系统安装和ros安装踩坑
  10. 设计模式——建造者模式 1
  11. C# 访问数据的时候报错 (拒绝了对对象 'XXXX' (数据库 'SHQY',架构 'dbo')的 SELECT 权限)...
  12. 【问题解决】VS Code官网下载速度慢的解决办法
  13. centos7 禁止ip访问_centos7下使用iptables屏蔽所有中国IP
  14. php 截掉最后一个字符_php 截取并删除字符串最后一个字符的方法
  15. 高性能台式计算机一体机,一体机电脑与台式机电脑,究竟选哪个好?
  16. 王树尧老师运筹学课程笔记 02 高等数学基础
  17. #日常---恒权码与变权码
  18. 1063计算谱半径(满分python)
  19. 手机如何备份数据在NAS里面?
  20. Qt输出缓冲区大小设置

热门文章

  1. 服务端向客户端主动发送消息
  2. Ubuntu 16.04如何使用无线网卡上网
  3. python交换机ssh巡检_python自动巡检H3C交换机
  4. 考试酷c语言程序设计的答案大全,FX-TRN-BEG-C 考试酷 V-MECA组合在PLC项目教学中的运用...
  5. NOIP2004P4 火星人 题解
  6. 怎么成为解决问题的高手
  7. 期权系统搭建-期权软件开发
  8. AI遮天传 DL-多层感知机
  9. 【机器学习】word2vec学习笔记(三):word2vec源码注释
  10. 2020/09-2020/11 读书摘录