数列分块入门1-9 By hzwer
声明
持续更新,因为博主也是正在学习分块的知识,我很菜的,菜的抠$jio$
写在前面
分块是个很暴力的算法,但却比暴力优秀的多,分块算法的时间复杂度一般是根号的,他的主要思想是将一个长度是$n$的数列分为$m$个块,在每个块上维护一些东西,询问的时候才会用到这些维护的东西,就像线段树中的懒标记一样。
Loj #6297. 数列分块入门1
这道题目是分块最基础的题目,仅需要支持区间加法和单点查询两个操作。
将这个数列分为$\sqrt{n}$块。同时可以确定每个块的左右端点。然后在于处理一个数组$in$,表示第$i$个元素在第$in[i]$块内。
再用一个$addtag$数组表示每个块的加法标记,到最后查询的时候会用到。
下面开始进行区间修改操作的讲解
假设给定的要修改序列的左右端点为$l$和$r$,那么可能会有三种情况出现,
- $l$和$r$在同一个块内,那么只需要暴力的修改l到r便可。
- $l$和$r$不在同一个块,那么要分别对$l$和$r$所在的块进行修改。
- 这一种是第$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的更多相关文章
- loj 6278 6279 数列分块入门 2 3
参考:「分块」数列分块入门1 – 9 by hzwer 2 Description 给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间加法,询问区间内小于某个值\(x\)的元素个数. 思 ...
- LOJ——#6277. 数列分块入门 1
~~推荐播客~~ 「分块」数列分块入门1 – 9 by hzwer 浅谈基础根号算法——分块 博主蒟蒻,有缘人可直接观摩以上大佬的博客... #6277. 数列分块入门 1 题目大意: 给出一个长为 ...
- LOJ 6277:数列分块入门 1(分块入门)
#6277. 数列分块入门 1 内存限制:256 MiB时间限制:100 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: hzwer 提交提交记录统计讨论 3 测试数据 题目描述 给出一 ...
- LOJ #6285. 数列分块入门 9-分块(查询区间的最小众数)
#6285. 数列分块入门 9 内存限制:256 MiB时间限制:1500 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: hzwer 提交提交记录统计测试数据讨论 2 题目描述 给 ...
- LOJ #6284. 数列分块入门 8-分块(区间查询等于一个数c的元素,并将这个区间的所有元素改为c)
#6284. 数列分块入门 8 内存限制:256 MiB时间限制:500 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: hzwer 提交提交记录统计测试数据讨论 2 题目描述 给出 ...
- LOJ #6283. 数列分块入门 7-分块(区间乘法、区间加法、单点查询)
#6283. 数列分块入门 7 内存限制:256 MiB时间限制:500 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: hzwer 提交提交记录统计测试数据讨论 2 题目描述 给出 ...
- LOJ #6282. 数列分块入门 6-分块(单点插入、单点查询、数据随机生成)
#6282. 数列分块入门 6 内存限制:256 MiB时间限制:500 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: hzwer 提交提交记录统计测试数据讨论 1 题目描述 给出 ...
- LOJ #6281. 数列分块入门 5-分块(区间开方、区间求和)
#6281. 数列分块入门 5 内存限制:256 MiB时间限制:500 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: hzwer 提交提交记录统计测试数据讨论 5 题目描述 给出 ...
- LOJ #6280. 数列分块入门 4-分块(区间加法、区间求和)
#6280. 数列分块入门 4 内存限制:256 MiB时间限制:500 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: hzwer 提交提交记录统计测试数据讨论 题目描述 给出一个 ...
随机推荐
- 局部优化与整体效果 新增时间>节省时间 权衡利弊
原代码 from selenium import webdriverimport requests,timeurl_l=[]with open('DISTINCT_url.txt', 'r', enc ...
- 用js采集网页数据并插入数据库最快的方法
今天教大家一个快速采集网站数据的方法,因为太晚了,直接上例子,这里以采集易车网的产品数据为例. 思路:利用js获取网页数据并生成sql命令,执行sql命令把采集的数据插入数据库. 1.用谷歌浏览器或者 ...
- finger的使用
finger命令用来查询一台主机上的登录账号的信息,通常会显示用户名.主目录.停滞时间.登录时间.登录Shell等信息,使用权限为所有用户. 安装 sudo apt-get install finge ...
- Keepalived+lvs 搭建高可用负载均衡
本站点停止更新,请访问:blog.coocap.com 不了解负载均衡高可用的童鞋,强烈建议先看keepalived+nginx高可用负载均衡: 传送门(求粉):http://www.cnblogs. ...
- Integer值判断是否相等问题
昨天在开发中遇到一个问题,定义了两个Integer变量,暂且定义为Integer a; Integer b; 这两个值由前端赋值并传到后台,前台传的是a = 12345, b = 12345, 但 ...
- E20170528-ts
partial adj. 部分的; 偏爱的; 偏袒的; 钟爱的; form n. 表格; 方式; 形状,形式; 外形 annotate vt. 注解,注释; n. 注释者; annotat ...
- bzoj 1626: [Usaco2007 Dec]Building Roads 修建道路【最小生成树】
先把已有的边并查集了,然后MST即可 记得开double #include<iostream> #include<cstdio> #include<algorithm&g ...
- P3573 [POI2014]RAJ-Rally
传送门 很妙的思路 首先这是一个DAG,于是我们先在原图和反图上各做一遍,分别求出\(diss_i\)和\(dist_i\)表示从\(i\)点出发的最短路和以\(i\)为终点的最短路 我们考虑把点分为 ...
- mybatis时间查询小技巧
网上大多数使用mybatis查询的时候都是把时间转换成Date使用的,其实这里时可以直接使用String的,比如 <if test="startTime != null and st ...
- 洛谷 P1414 又是毕业季II(未完成)
题目背景 “叮铃铃铃”,随着高考最后一科结考铃声的敲响,三年青春时光顿时凝固于此刻.毕业的欣喜怎敌那离别的不舍,憧憬着未来仍毋忘逝去的歌.1000多个日夜的欢笑和泪水,全凝聚在毕业晚会上,相信,这一定 ...