声明

持续更新,因为博主也是正在学习分块的知识,我很菜的,菜的抠$jio$

写在前面

分块是个很暴力的算法,但却比暴力优秀的多,分块算法的时间复杂度一般是根号的,他的主要思想是将一个长度是$n$的数列分为$m$个块,在每个块上维护一些东西,询问的时候才会用到这些维护的东西,就像线段树中的懒标记一样。

Loj #6297.  数列分块入门1

这道题目是分块最基础的题目,仅需要支持区间加法和单点查询两个操作。

将这个数列分为$\sqrt{n}$块。同时可以确定每个块的左右端点。然后在于处理一个数组$in$,表示第$i$个元素在第$in[i]$块内。

再用一个$addtag$数组表示每个块的加法标记,到最后查询的时候会用到。

下面开始进行区间修改操作的讲解

假设给定的要修改序列的左右端点为$l$和$r$,那么可能会有三种情况出现,

  1. $l$和$r$在同一个块内,那么只需要暴力的修改l到r便可。
  2. $l$和$r$不在同一个块,那么要分别对$l$和$r$所在的块进行修改。
  3. 这一种是第$2$种的一个分支情况,就是$l$和$r$之间还有很多个块,因为这些块都是被整体修改,所以直接打到标记上就行。

下面给出这道题的代码

神呐,原谅我的码风放荡不羁

#include <iostream>
#include <cstdio>
#include <cmath>
typedef long long LL;
const int maxn = ;
int n, opt, a, b, c, w[maxn], cnt, in[maxn], addtag[maxn];
inline int read() {
int x = , f = ; char ch = getchar();
while (ch < '' || ch > '') {if(ch == '-') f = -;ch = getchar();}
while (ch <= '' && ch >= '') {x = x * + ch - '';ch = getchar();}
return x * f;
}
inline void add(int l, int r, int x) {
for(int i=l; i<=std::min(r, in[l]*cnt); i++)
w[i] += x;
if(in[l] != in[r])
for(int i=(in[r]-)*cnt+; i<=r; i++)
w[i] += x;
for(int i=in[l]+; i<in[r]; i++)
addtag[i] += x;
}
inline int query(int x) {
return w[x] + addtag[in[x]];
}
int main() {
n = read();
cnt = std::sqrt(n);
for(int i=; i<=n; i++) w[i] = read();
for(int i=; i<=n; i++) in[i] = (i-)/cnt+;
for(int i=; i<=n; i++) {
opt = read(), a = read(), b = read(), c = read();
if(opt == ) add(a, b, c);
else printf("%d\n", query(b));
}
}

先到这里,明天续更QAQ

Loj #6298.  数列分块入门2

这次要维护的东西多了一个找区间内小于某个值的数的个数。但题目的做法和上面的非常的相似。

让每个块在查询的时候都变成有序的,我们就可以二分查找来找到第一个大于给定数值的数的位置,然后就可以算出一个块内有多少数是小于给定值的。

对于区间假发的操作和第一题的一样,唯一要加上的就是每次序列改变后都要进行排序,来保证序列是有序的。

代码看下面

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
typedef long long LL;
const int maxn = 5e4+;
std::vector <int> blo[];
int n, w[maxn], opt, a, b, c, cnt, in[maxn], addtag[maxn];
inline LL read() {
LL x = , f = ; char ch = getchar();
while (ch < '' || ch > '') {if(ch == '-') f = -; ch = getchar();}
while (ch <= '' && ch >= '') {x = x* + ch-''; ch = getchar();}
return x * f;
}
inline void resort(int x) {
blo[x].clear();
for(int i=(x-)*cnt+; i<=x*cnt; i++)
blo[x].push_back(w[i]);
std::sort(blo[x].begin(), blo[x].end());
}
inline void add(int l, int r, int x) {
for(int i=l; i<=std::min(in[l]*cnt, r); i++)
w[i] += x;
resort(in[l]);
if(in[l] != in[r])
for(int i=(in[r]-)*cnt+; i<=r; i++)
w[i] += x;
resort(in[r]);
for(int i=in[l]+; i<in[r]; i++)
addtag[i] += x;
}
inline int query(int l, int r, int x) {
int Ans = ;
for(int i=l; i<=std::min(r, in[l]*cnt); i++)
if(w[i] + addtag[in[i]] < x) Ans ++;
if(in[l] != in[r])
for(int i=(in[r]-)*cnt+; i<=r; i++)
if(w[i] + addtag[in[i]] < x) Ans ++;
for(int i=in[l]+; i<in[r]; i++){
int s = x-addtag[i];
Ans += std::lower_bound(blo[i].begin(), blo[i].end(), s)-blo[i].begin();
}
return Ans;
}
int main() {
n = read();
cnt = std::sqrt(n);
for(int i=; i<=n; i++) w[i] = read();
for(int i=; i<=n; i++) {
in[i] = (i-)/cnt+;
blo[in[i]].push_back(w[i]);
}
for(int i=; i<=in[n]; i++)
std::sort(blo[i].begin(), blo[i].end());
for(int i=; i<=n; i++) {
opt = read(), a = read(), b = read(), c = read();
if(opt == ) add(a, b, c);
else printf("%d\n", query(a, b, c*c));
}
}

Loj #6279.  数列分块入门3

这一题在上一题的基础上稍作变化,将询问操作变为求每一个数在某一个区间内的前驱

一个数的前驱的定义为小于这个数的第一个数。这里要提到一点就是在每一个块中可以通过维护其他的数据结构来实现一些其他的操作

这里就维护了一个不可重集合$set$。

大部分操作和上题一样,只有查询稍有不同,说一下查询。

定义一个迭代器(指针)。$set$是有序的所以不需要排序,二分查找$x$。那么$x$前面的第一个数就是它的前驱。

显然,如果$x=l$,那$x$就没有前驱。

还是放上代码

#include <iostream>
#include <cstdio>
#include <cmath>
#include <set>
#include <algorithm>
const int maxn = 1e5+;
typedef long long LL;
int n, w[maxn], addtag[], in[maxn], opt, a, b, c, cnt;
std::set <int> blo[];
inline LL read() {
LL x = , f = ; char ch = getchar();
while (ch < '' || ch > '') {if(ch == '-') f = -; ch = getchar();}
while (ch <= '' && ch >= '') {x = x* + ch-''; ch = getchar();}
return x * f;
}
inline void add(int l, int r, int x) {
for(int i=l; i<=std::min(r, in[l]*cnt); i++) {
blo[in[l]].erase(w[i]);
w[i] += x;
blo[in[l]].insert(w[i]);
}
if(in[l] != in[r])
for(int i=(in[r]-)*cnt+; i<=r; i++) {
blo[in[r]].erase(w[i]);
w[i] += x;
blo[in[r]].insert(w[i]);
}
for(int i=in[l]+; i<in[r]; i++)
addtag[i] += x;
}
inline int query(int l, int r, int x) {
int ans = -;
for(int i=l; i<=std::min(in[l]*cnt, r); i++)
if(w[i] + addtag[in[i]] < x)
ans = std::max(ans, w[i]+addtag[in[i]]);
if(in[l] != in[r])
for(int i=(in[r]-)*cnt+; i<=r; i++)
if(w[i] + addtag[in[i]] < x)
ans = std::max(ans, w[i]+addtag[in[i]]);
for(int i=in[l]+; i<in[r]; i++) {
int s = x - addtag[i];
std::set<int>::iterator it = blo[i].lower_bound(s);
if(it == blo[i].begin()) continue;
--it;
ans = std::max(ans, *it+addtag[i]);
}
return ans;
}
int main() {
n = read();
cnt = ;
for(int i=; i<=n; i++) w[i] = read();
for(int i=; i<=n; i++) {
in[i] = (i-)/cnt+;
blo[in[i]].insert(w[i]);
}
for(int i=; i<=n; i++){
opt = read(), a = read(), b = read(), c = read();
if(opt == ) add(a, b, c);
else printf("%d\n", query(a, b, c));
}
}

Loj #6280.  数列分块入门4

现在又要求支持区间求和,其实不难,每一个块内都维护一个$sum$表示这个块内所有元素的和。

在进行修改区间两端的两个特殊的块的时候修改一个加一个。

到最后查询的时候$sum+addtag*cnt$,$cnt$表示块的大小。

代码长这个样子

#include <iostream>
#include <cstdio>
#include <cmath>
const int maxn = 5e4+;
typedef long long LL;
int n, cnt, in[maxn], opt, l, r, k, arr[maxn], addtag[], sum[];
inline LL read() {
LL x = , f = ; char c = getchar();
while (c < '' || c > '') {if(c == '-') f = -; c = getchar();}
while (c <= '' && c >= '') {x = x* + c-''; c = getchar();}
return x * f;
}
inline void add(int l, int r, int k) {
for(int i=l; i<=std::min(r, in[l]*cnt); i++)
arr[i] += k, sum[in[i]] += k;
if(in[l] != in[r])
for(int i=(in[r]-)*cnt+; i<=r; i++)
arr[i] += k, sum[in[i]] += k;
for(int i=in[l]+; i<in[r]; i++)
addtag[i] += k;
}
inline int query(int l, int r, int Mod) {
int ans = ;
for(int i=l; i<=std::min(r, in[l]*cnt); i++)
ans = arr[i] % Mod + addtag[in[i]] % Mod + ans % Mod;
if(in[l] != in[r])
for(int i=(in[r]-)*cnt+; i<=r; i++)
ans = arr[i] % Mod + addtag[in[i]] % Mod + ans % Mod;
for(int i=in[l]+; i<in[r]; i++)
ans = addtag[i] % Mod * cnt % Mod + sum[i] % Mod + ans % Mod;
return ans % Mod;
}
int main() {
n = read();
cnt = std::sqrt(n);
for(int i=; i<=n; i++) {
arr[i] = read();
in[i] = (i-)/cnt+;
sum[in[i]] += arr[i];
}
for(int i=; i<=n; i++) {
opt = read(), l = read(), r = read(), k = read();
if(opt == ) add(l, r, k);
else printf("%d\n", query(l, r, k+));
}
}

Loj #6281.  数列分块入门5

支持区间开方和区间查询。

显然区间开方是最难处理的地方,我去参考了一下黄学长的博客,得到了一种比较神奇的方法(原谅我见识少),一段区间进行至多$5$次区间开方就会变成$1$或者$0$。

那么我们只需要暴力的对区间进行修改,由于$1$和$0$开方后还是本身,所以对于已经全部变为$1$或者$0$的区间就不必再开方了

附上代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
const int maxn = 5e4+;
int n, cnt, in[maxn], arr[maxn], sum[], opt, l, r, k;
bool flag[];
inline int read() {
int x = , f = ; char c = getchar();
while (c < '' || c > '') {if(c == '-') f = -; c = getchar();}
while (c <= '' && c >= '') {x = x* + c-''; c = getchar();}
return x * f;
}
inline void solve_sqrt(int x) {
if(flag[x]) return ;
flag[x] = ;
sum[x] = ;
for(int i=(x-)*cnt+; i<=x*cnt; i++) {
arr[i] = std::sqrt(arr[i]);
sum[x] += arr[i];
if(arr[i] > ) flag[x] = ;
}
}
inline void update(int l, int r) {
for(int i=l; i<=std::min(r, in[l]*cnt); i++) {
sum[in[i]] -= arr[i];
arr[i] = std::sqrt(arr[i]);
sum[in[i]] += arr[i];
}
if(in[l] != in[r]) {
for(int i=(in[r]-)*cnt+; i<=r; i++) {
sum[in[i]] -= arr[i];
arr[i] = std::sqrt(arr[i]);
sum[in[i]] += arr[i];
}
}
for(int i=in[l]+; i<in[r]; i++)
solve_sqrt(i);
}
inline int query(int l, int r) {
int ans = ;
for(int i=l; i<=std::min(in[l]*cnt, r); i++)
ans += arr[i];
if(in[r] != in[l])
for(int i=(in[r]-)*cnt+; i<=r; i++)
ans += arr[i];
for(int i=in[l]+; i<in[r]; i++)
ans += sum[i];
return ans;
}
int main() {
n = read();
cnt = std::sqrt(n);
for(int i=; i<=n; i++)
arr[i] = read(), in[i] = (i-)/cnt + ;
for(int i=; i<=n; i++)
sum[in[i]] += arr[i];
for(int i=; i<=n; i++) {
opt = read(), l = read(), r = read(), k = read();
if(opt == ) update(l, r);
else printf("%d\n", query(l, r));
}
}

数列分块入门1-9 By hzwer的更多相关文章

  1. loj 6278 6279 数列分块入门 2 3

    参考:「分块」数列分块入门1 – 9 by hzwer 2 Description 给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间加法,询问区间内小于某个值\(x\)的元素个数. 思 ...

  2. LOJ——#6277. 数列分块入门 1

    ~~推荐播客~~ 「分块」数列分块入门1 – 9 by hzwer 浅谈基础根号算法——分块 博主蒟蒻,有缘人可直接观摩以上大佬的博客... #6277. 数列分块入门 1 题目大意: 给出一个长为 ...

  3. LOJ 6277:数列分块入门 1(分块入门)

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

  4. LOJ #6285. 数列分块入门 9-分块(查询区间的最小众数)

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

  5. LOJ #6284. 数列分块入门 8-分块(区间查询等于一个数c的元素,并将这个区间的所有元素改为c)

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

  6. LOJ #6283. 数列分块入门 7-分块(区间乘法、区间加法、单点查询)

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

  7. LOJ #6282. 数列分块入门 6-分块(单点插入、单点查询、数据随机生成)

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

  8. LOJ #6281. 数列分块入门 5-分块(区间开方、区间求和)

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

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

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

随机推荐

  1. C#与excel互操作的错误无法将类型为“Microsoft.Office.Interop.Excel.ApplicationClass”的 COM 对象强制

    C#与excel互操作的错误无法将类型为“Microsoft.Office.Interop.Excel.ApplicationClass”的 COM 对象强制 如果您使用的电脑要操作的是office2 ...

  2. 怎么样关掉红米note开发者选项

    进 系统设置\应用 ,找到“设置”点进去,清一下数据,再打开“设置”查看,就没有“开发者选项”了

  3. RDA DEBUG

    DEBUG寄存器:word 0xa0000010 word 0xa0000010 1 //debug开 word 0xa0000010 0 //debug关 当然也可以按模块打开/关闭debug信息, ...

  4. mipi差分信号原理

    差分信号,什么是差分信号 一个差分信号是用一个数值来表示两个物理量之间的差异.从严格意义上来讲,所有电压信号都是差分的,因为一个电压只能是相对于另一个电压而言的.在某些系统里,系统’地’被用作电压基准 ...

  5. bat 截取字符串(for命令) 推荐收藏

    摘自:http://www.jb51.net/article/50354.htm 今天需要用批处理命令处理文件夹中的文件,需要用到bat中的for命令以及字符串截取的一些命令.在上面的链接中找到许多有 ...

  6. 查看mysql数据库的所有配置信息和服务器的各种状态

    查看MySQL服务器配置信息 mysql> show variables; 2, 查看MySQL服务器运行的各种状态值 mysql> show global status;

  7. Eclipse导入Java 的jar包的方法

    打开eclipse1.右击要导入jar包的项目,点properties 2.左边选择java build path,右边选择libraries 3.选择add External jars 4.选择ja ...

  8. 每天一个 linux命令(35):ln 命令(转载)

    转载:http://www.cnblogs.com/peida/archive/2012/12/11/2812294.html ln是linux中又一个非常重要命令,它的功能是为 某一个文件在另外一个 ...

  9. 项目需求会__前端er定位的思考~

    一.页面展示-----针对前端部分:后台的东西(功能.样式)不考虑! 二.动态效果------能不能实现! 三.接口数据------怎么传数据! 四.兼容性--------兼容到哪个版本浏览器! 五. ...

  10. [ Luogu 4917 ] 天守阁的地板

    \(\\\) \(Description\) 定义二元函数\(F(x,y)\)表示,用 \(x\times y\) 的矩形不可旋转的铺成一个任意边长的正方形,所需要的最少的矩形个数. 现在\(T\)组 ...