声明

持续更新,因为博主也是正在学习分块的知识,我很菜的,菜的抠$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. visual studio , JavaScript , UnitTest

    https://docs.microsoft.com/en-us/visualstudio/cross-platform/tools-for-cordova/debug-test/basic-test ...

  2. 【HDU 2157】 How Many Ways??

    [题目链接] 点击打开链接 [算法] 设A[i][j]为走一条边,从i走到j的方案数 C[i][j]为走两条边,从i走到j的方案数,显然有 : C = A * A = A^2 C'[i][j]为走三条 ...

  3. NOI.AC #31 MST —— Kruskal+点集DP

    题目:http://noi.ac/problem/31 好题啊! 题意很明白,对于有关最小生成树(MST)的题,一般是要模拟 Kruskal 过程了: 模拟 Kruskal,也就是把给出的 n-1 条 ...

  4. gitlab邮箱验证 邮箱提醒设置

    Gitlab邮件提醒方便跟踪项目进度,在这里介绍两种方式,一种是用系统的sendmail发送邮件,另一种是GMAIL的stmp来发送邮件   第一种 用系统的sendmail发送邮件   cd /ho ...

  5. 27. FormPanel类的defaults属性

    defaults : Object defaults属性可以包含任意个name/value属性对,这些属性将会被添加到每一个元素中...例如, 为了自动向容器包含的每个Ext.Panel 元素的宽度添 ...

  6. asp.net mvc6学习资料整理

    十分钟轻松让你认识ASP.NET MVC6 http://www.cnblogs.com/n-pei/p/4272105.html ASP.NET 5系列教程 (六): 在 MVC6 中创建 Web ...

  7. 两个局域网(办公网-IDC)安全互通方案2:by GRE and linux server&深入理解GRE

    (0)gre的turnel的打通 1. 这个过程就是双方建立turnel的过程.           (1)局域网路由过程 1.主机A发送一个源为192.168.1.2,目的为10.1.1.2的包 ( ...

  8. robotframework - create dictionary 操作

    1.创建字典 2.从字典中获取的项 -- 打印出 item 3.获取字典的key -- 打印出 key 4.获取字典的value -- 打印出 value 5.获取字典key,value 6.打印出字 ...

  9. Linux学习之01_基础命令介绍

    初学Linux,还在摸索中,在这个过程中希望能记录下学习到的东西,参考的的书籍为<鸟哥的Linux私房菜> 在这里学到的主要命令有这几个: data cal bc man shutdown ...

  10. 洛谷 P1233 木棍加工

    题目描述 一堆木头棍子共有n根,每根棍子的长度和宽度都是已知的.棍子可以被一台机器一个接一个地加工.机器处理一根棍子之前需要准备时间.准备时间是这样定义的: 第一根棍子的准备时间为1分钟: 如果刚处理 ...