声明

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

    ffmpeg缩小视频尺寸 | 楚盟博客 https://www.5yun.org/13126.html ffmpeg -i test.mp4 -s 480×360 out.mp4 常用分辨率: 108 ...

  2. VC/MFC列表CListCtrl类的LVCOLUMN和LVITEM详解

      列表视图控件(List Control)列表视图控件是一种非常常用的控件,在需要以报表形式显示数据时,列表控件通常是最好的选择,许多专用的数据报表控件,也是在它的基础上派生而来.与树视图类似,列表 ...

  3. vue 目录结构介绍

    1 初始目录如下: 2 目录结构介绍 bulid:最终帆布的代码存放位置 config:配置目录,包括端口号等 node_modules:npm加载的项目依赖模块 src:z这里是我们要开发的目录,基 ...

  4. 3-4章 第3章 form表单组件与小程序前后端通信

    View它相当于是一个点击触发一个事件,但是它的事件应该是相对来说可能是比较是偏向于页面上的一些展示,或者说是页面上的一些导航的一些跳转.Button它是一个标签, button是一个标签,一般去触发 ...

  5. C++中的常量(一) const限定符

    最近在重新看<<C++ Primer>>,第一遍的时候const和constexpr看得并不太懂,这次又有了些更新的理解,当然可能仍然有许多不对的地方... 首先,const限 ...

  6. Android 网络请求及数据处理

    Android 网络请求: 1.Volley   http://blog.csdn.net/t12x3456/article/details/9221611 2.Android-Async-Http  ...

  7. Rails 确认params的统一方法

    创建: 2017/11/06    Gemfile  ### デバッグ出力の整形  gem 'awesome_print', :group => [:development, :test]  a ...

  8. 栗染-Error parsing D:\sdkforas\android-sdk-windows\system-images\android-24\android-wear\x86\devices.xml

    每次打开android virtual device manager 下面都会出现这样的问题 解决办法: 打开自己安装的sdk目录,找到/tools/lib/devices.xml去替换图中路径里面的 ...

  9. bzoj 1704: [Usaco2007 Mar]Face The Right Way 自动转身机【贪心+差分】

    首先O(n^3)的贪心很好想,就是枚举k然后从前往后扫,扫到反就翻转区间 然后考虑优化掉翻转区间维,就是搞成差分的形式,在翻转区间的尾部打上标记,再用一个变量维护当前的翻转次数,加到当前状态上来判断是 ...

  10. cookie使用详解

    cookie是用来保存客户资料的好方法,与同样可以用来保存客户资料的 session不同的是,session是把资料保存在服务器端,而cookie是把资料保存在客户端,我们平常接触的最多的cookie ...