闲话

莫队算法似乎还是需要一点分块思想的。。。。。。于是我就先来搞分块啦!

膜拜hzwer学长神犇%%%Orz

这九道题,每一道都堪称经典,强力打Call!点这里进入

算法简述

每一次考试被炸得体无完肤之后,又听到讲题目的Dalao们爆出一句

数据不大,用分块暴力搞一下就AC了

的时候,我就会五体投地,不得翻身。。。。。。

分块?暴力?真的有如此玄学?

直到现在我还是觉得它很笨拙,但要熟练运用(尤其是打大暴力部分分的时候),绝非易事。

没错,分块是优化的暴力,更轻松地资瓷在线,是一种算法思想,主要用来维护区间信息,因此也可以当成数据结构。

当绞尽脑汁也无法发现能用自带log的数据结构(线段树,树状数组,平衡树······)维护的时候,一定不要抛弃它。

一般的分块题目形式,都是给你若干个区间处理操作。这些区间有长有短,直接暴力操作复杂度是\(O(n^2)\)的。

分块就是为了解决区间过长所导致的低效而出现的。

具体做法,是把数列分成若干个小块,每个小块内部,每个元素的值仍然需要维护,然后再维护整个小块的信息。

对于一个很长的操作区间,其内部一定会包含若干连续的小块对吧。

对这些小块,直接对小块信息操作,不用一个个操作每个元素啦,从而省去了大量时间。

当然,区间两头会有不包含一个完整小块的部分,暴力修改。然而这两头长度加起来不会超过块的长度的两倍,复杂度有保证。

常见题目类型&套路&难度分析(持续更新中)

  1. 维护块信息总和★★☆☆☆

    最裸的一类啦。。。。。。

    例题有下面hzwer九题中的1、4

  2. 维护块标记并进行懒处理★★★☆☆

    需要分析复杂度的思路,巧妙地利用题目特点,维护好懒标记并正确释放,但实现难度不高

    例题有下面hzwer九题中的5、7和8

  3. 块内维护其它结构★★★☆☆

    思路较清晰,但实现难度加大,常见的类型(我只见过的)有

    —— 有序表,以便资磁块内二分答案等操作,如下面hzwer九题中的2、3

    —— 链表,可以方便地插入和删除,更可以高效完成块与块的合并或分裂(重构),如下面hzwer九题中的6

    —— 平衡树,更多高级操作,题目仍在发现中(当然,可以把hzwer九题中的3、6两题的操作合在一起)(手动滑稽)

    —— 。。。。。。

  4. 维护块区间的信息★★★★☆

    解释一下,这里的“块区间”指的是第\(l\)个块到第\(r\)个块的信息。如果需要这样维护的话,通常思维难度和实现难度都上了一个档次。

    题目仍在发现并落实中。。。。。。(hzwer九题中的9当然是个火题辣,不过利用了离线我用常数更优的莫队水了一下)

题目列表

hzwer数列分块入门九题

hzwer的题解都很详细了,我再写点自己的东西吧。

分块入门 1 by hzwer

LOJ题目传送门

题面等于洛谷模板树状数组2。。。。。。

不过既然学分块就好好学嘛。

这里的小块信息就是一个加法标记,表示对整个区间的所有数加上标记值。

于是做法就很简单了。

对于这个分块长度(以下设为\(m\))的问题,我不是很清楚。对于每个具体的题目,到底该取多少呢?

方便的话,默认\(m=\sqrt n\)吧。

分析一下单次修改的复杂度。在本题中,整块修改复杂度取决于块的数量,复杂度\(O({n\over m})\);

不完整块暴力修改复杂度取决于块的长度,复杂度\(O(m)\)。

根据基本不等式,\({n\over m}+m≥2\sqrt n\)当且仅当\({n\over m}=m\)时等号成立。

于是\(m=\sqrt n\)。

当然,如果数据是随机的,那么平均情况下的系数考虑一下会得到更好的\(m\)。

首先,询问区间的平均长度是\(n\over 3\)(试出来的,我太弱了不会证,欢迎Dalao指教)

然后,暴力修改的部分平均长度肯定是\(m\)(\({m\over 2}×2\)),得到带系数的复杂度\(O({n\over {3m}}+m)\)。

再次用基本不等式得到更优的\(m\)是\(\sqrt{{n\over 3}}\)。

目测hzwer的数据是rand的,所以改了以后确实快不少。

贴下代码吧。貌似我的代码永远是最短最丑的。。。。。。

#include<cstdio>
#include<cmath>
int a[50009],b[233];//b为小块加法标记
int main()
{
register int n,L,i,op,l,r,c;
scanf("%d",&n);
L=sqrt(n/3);
for(i=0;i<n;++i)scanf("%d",&a[i]);
for(i=0;i<n;++i)
{
scanf("%d%d%d%d",&op,&l,&r,&c);
--l;--r;//为了方便,数组从0下标开始存了
if(op)printf("%d\n",a[r]+b[r/L]);
else
{
for(;l<=r&&l%L;++l)a[l]+=c;//暴力(左)
for(;l+L-1<=r;l+=L)b[l/L]+=c;//改块
for(;l<=r;++l)a[l]+=c;//暴力(右)
}
}
return 0;
}

分块入门 2 by hzwer

LOJ题目传送门

这时候,分块成为了最优解法。

排序预处理,询问操作块内二分,块外暴力。

修改操作块内放加法标记,块外暴力加排序重构。

平均情况下复杂度大致为\(O(n\log m+n({n\over m}\log m+m\log m))\)

利用平均系数\(m\)同样可取\(\sqrt{{n\over 3}}\)

实际上我尝试了一下,\(\sqrt{{n\over 4}}\)即\(\sqrt n\over 2\)更快。

因为预处理中带了个\(m\),加上修改操作中也带有\(m\)而在上式中为了简便被省略了。

所以\(m\)适当的更小一点。

还有一点,其实修改操作时对两个不完整部分的重新排序,可以不用写sort。

原块已经排好序,修改相当于给这个有序序列部分元素加上一个相等的值。

那么现在这个序列可以分成两个集合——被加的和没被加的。可以看出这两个集合按在原序列的位置中,仍然是各自有序的。

对两个有序的序列再排序,最高效的不就是二路归并吗?

这样做的话,要在有序序列中额外维护每个元素在原序列中的位置。

扫一遍这个不完整块的时候,根据位置判断是否被加,然后放入对应集合。最后对两个集合归并排序放回有序序列即可。

仅仅对单次修改操作而言,这样做使得复杂度降至\(O({n\over m}+m)\),去掉了\(\log\)。

然而这种写法也有一点常数,也比较麻烦,我就偷了点懒QvQ

上代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define R register
char s[1314520];
int a[54250],b[54250],t[2333];//请忽略丑陋的数组长度定义
#define resort(P) {memcpy(b+P,a+P,L<<2);sort(b+P,b+P+L);}//重构排序
#define in(z) {while(*++p<'-');z=*p&15;while(*++p>'-')z*=10,z+=*p&15;}
int main(){
fread(s,1,sizeof(s),stdin);
R char*p=s-1;
R int n,L,i,op,l,r,c,ans;
in(n);
L=sqrt(n)/2;
for(i=0;i<n;++i)in(a[i]);
for(;i%L;++i)a[i]=1e9;//人工在最末块尾部插入inf,避免vector带来的常数
for(i=0;i<n;i+=L)resort(i);//初始化排序
for(i=0;i<n;++i){
in(op);in(l);in(r);in(c);
--l;--r;
if(op){
ans=0;c*=c;
for(;l<=r&&l%L;++l)ans+=a[l]+t[l/L]<c;//暴力(左)
for(;l+L-1<=r;l+=L)ans+=lower_bound(b+l,b+l+L,c-t[l/L])-b-l;//块内二分统计答案
for(;l<=r;++l)ans+=a[l]+t[l/L]<c;//暴力(右)
printf("%d\n",ans);
}
else{
if(l/L==r/L){//在同一个块中,暴力改完走人
for(;l<=r;++l)a[l]+=c;
resort(r/L*L);
continue;
}
if(l%L){//暴力(左)
for(;l%L;++l)a[l]+=c;
resort(l-L);
}
for(;l+L-1<=r;l+=L)t[l/L]+=c;//直接改
if(r>=l){//暴力(右)
for(;r>=l;--r)a[r]+=c;
resort(l);
}
}
}
return 0;
}

分块入门 3 by hzwer

LOJ题目传送门

基本思路与2差不多,除了二分统计答案的方式。

因此就没什么好说的啦,代码跟上面一样丑,就不贴了

分块入门 4 by hzwer

LOJ题目传送门

题面等于洛谷模板线段树1。。。。。。

相比1,再加上块内总和的维护就好啦,代码也不贴了

分块入门 5 by hzwer

LOJ题目传送门

其实分块不是正解,我还是惯性思维想到了线段树。

反正开方不会超过四次,那可以用带log的数据结构维护。

考虑线段树,弄个标记表示当前节点区间已全部变为0或1,假如两个子区间都打标记了,那么当前区间也打上,以后区间开方碰到有标记就结束掉。

这样复杂度是对的,\(O(N\log N)\)带上一个不小的常数,但总比分块好点。。。。。。

分块的话也就是在块内搞同样意义的标记嘛!似乎还要写链表,不是很轻松的暴力。。。。。。我还是太懒了

线段树代码(放在这里极其不和谐)

#include<cstdio>
#include<cmath>
#define R register
#define G c=getchar()
inline void in(R int&z){
R char G;
while(c<'-')G;
z=c&15;G;
while(c>'-')z*=10,z+=c&15,G;
}
const int M=3000000;
int le[M],mi[M],ri[M],s[M],t[M];
#define lc u<<1
#define rc u<<1|1
#define pushup s[u]=s[lc]+s[rc],t[u]=t[lc]&t[rc]//上传和以及标记
void build(R int u,R int l,R int r){//建树
le[u]=l;ri[u]=r;mi[u]=(l+r)>>1;
if(l==r){
in(s[u]);
if(s[u]<2)t[u]=1;
return;
}
build(lc,l,mi[u]);
build(rc,mi[u]+1,r);
pushup;
}
void update(R int u,R int l,R int r){//更新,略超出模板范围
if(t[u])return;
if(le[u]==ri[u]){
s[u]=sqrt(s[u]);
if(s[u]<2)t[u]=1;
return;
}
if(r<=mi[u])update(lc,l,r);
else if(l>mi[u])update(rc,l,r);
else update(lc,l,mi[u]),update(rc,mi[u]+1,r);
pushup;
}
int ask(R int u,R int l,R int r){//查询完全是模板
if(l==le[u]&&r==ri[u])return s[u];
if(r<=mi[u])return ask(lc,l,r);
else if(l>mi[u])return ask(rc,l,r);
else return ask(lc,l,mi[u])+ask(rc,mi[u]+1,r);
}
int main(){
R int n,op,l,r,c;
in(n);
build(1,1,n);
while(n--){
in(op);in(l);in(r);in(c);
if(op)printf("%d\n",ask(1,l,r));
else update(1,l,r);
}
return 0;
}

分块入门 6 by hzwer

LOJ题目传送门

总觉得像平衡树基本操作。。。。。。

这里引入了一个新的概念——重构。

为了方便,我手写了一下链表套链表(滑稽)

就是块与块之间用链表相连,块中的每个元素也用链表连起来。

我采用了区间过大重构的方法。

这样的话当区间过大时,直接新开一个块插入在块的链表中,再让其指向原块的中点位置元素,省了挺多事的。

查询的话就维护每个块的大小,从前往后扫,以此确定某点在哪一个块中,再在块内挨个找。

还有一个小小的idea,可以让靠前的块更大,靠后的块略小,复杂度被平均了,会降低一些(只是貌似带来了更大的常数。。。。。。)

代码

#include<cstdio>
#include<cmath>
#define G c=getchar()
#define in(z) G;\
while(c<'-')G;\
z=c&15;G;\
while(c>'-')z*=10,z+=c&15,G
const int B=1009,N=200009;
int s[B],neb[B],he[B],ne[N],v[N];
//neb块链表,he块首元素,ne元素链表,注意区分
int main()
{
register int n,L,i,j,b,p,pb,op,l,r;
register char c;
in(n);L=sqrt(n);
for(b=i=j=0;i<n;++i,++j){
in(v[i]);
if(j==L){
j=0;
neb[b]=b+1;
he[++b]=i;
}
ne[i]=i+1;
++s[b];
}
p=n-1;pb=b;//p新点,pb新块
while(n--){
in(op);in(l);in(r);in(j);
if(op){//查询,先扫块,再扫元素
for(b=0;s[b]<r;r-=s[b],b=neb[b]);
for(i=he[b],--r;r;i=ne[i],--r);
printf("%d\n",v[i]);
}
else{
for(b=0;s[b]<l;l-=s[b],b=neb[b]);
if(--l){
for(i=he[b],--l;l;i=ne[i],--l);
ne[++p]=ne[i],ne[i]=p;
}//找到插入位置并插入,注意特判块头
else ne[++p]=he[b],he[b]=p;
v[p]=r;
if(++s[b]>=L<<1){//重构,把块砍两半
s[++pb]=s[b]>>=1;
neb[pb]=neb[b];neb[b]=pb;//神奇的链表指来指去
for(i=he[b],l=L-1;l;i=ne[i],--l);
he[pb]=ne[i];
}
}
}
return 0;
}

分块入门 7 by hzwer

LOJ题目传送门

题面等于洛谷模板线段树2(不是模板,是大火题!)

已经敲完线段树2和Tree II,对此题失去了兴趣(其实是因为我太弱了,真的怕放标记又写挂。。。。。。)

分块入门 8 by hzwer

LOJ题目传送门

思路像5,但代码不易,尤其是每次暴力左右不完整部分、破坏了块的同一个值的时候,还要释放标记,写起来是真心累。。。。。。

长长的代码

#include<cstdio>
#include<cmath>
#include<iostream>
using namespace std;
#define G ch=getchar()
#define in(z) G;\
while(ch<'-')G;\
z=ch&15;G;\
while(ch>'-')z*=10,z+=ch&15,G
int a[100009],t[1009];
int main(){
register int n,L,i,j,l,r,c,bel,ber,ans;
register char ch;
in(n);L=sqrt(n);
for(i=0;i<n;++i){in(a[i]);}
for(j=0;j<n;++j){
in(l);in(r);in(c);--l;//所有下标减了1,并强行转化成左闭右开
bel=l/L;ber=r/L;ans=0;
//下面真的是一大堆讨论,为了减少代码量,把很多情况合并到一起处理了
if(bel!=ber){
if(l%L){//不完整(左)
if(t[bel]){
if(c==t[bel]){//与标记相等
ans+=(bel+1)*L-l;l=(bel+1)*L;
goto M;
}
for(i=bel*L;i<l;++i)a[i]=t[bel];//不相等,放标记
t[bel]=0;
for(i=l,l=(bel+1)*L;i<l;++i)a[i]=c;//暴力改
}
else for(i=l,l=(bel+1)*L;i<l;++i)
a[i]==c?++ans:a[i]=c;//暴力
}
else --bel;
M:while(++bel<ber){//完整
l+=L;
if(t[bel]){
if(c==t[bel]){
ans+=L;
continue;
}
}
else for(i=l-L;i<l;++i)
a[i]==c?++ans:a[i]=c;
t[bel]=c;//printf("bel%d ans%d\n",bel,ans);
}
}
if(l==r)goto N;//无不完整(右)
if(t[bel]){//在不完整的同一块中,或者不完整(右)
if(c==t[bel]){
ans+=r-l;
goto N;
}
for(i=bel*L;i<l;++i)a[i]=t[bel];//两边都要放
for(i=l;i<r;++i)a[i]=c;
for(i=min((bel+1)*L,n)-1;i>=r;--i)a[i]=t[bel];
t[bel]=0;
}
else for(i=l;i<r;++i)
a[i]==c?++ans:a[i]=c;
N:printf("%d\n",ans);
}
return 0;
}

持续更新中。。。。。。

数列分块总结——题目总版(hzwer分块九题及其他题目)(分块)的更多相关文章

  1. 数列分块入门九题(三):LOJ6283~6285

    Preface 最后一题我一直觉得用莫队是最好的. 数列分块入门 7--区间乘法,区间加法,单点询问 还是很简单的吧,比起数列分块入门 7就多了个区间乘. 类似于线段树,由于乘法的优先级高于加法,因此 ...

  2. 数列分块入门九题(二):LOJ6280~6282

    Preface 个人感觉这中间的三题是最水的没有之一 数列分块入门 4--区间加法,区间求和 这个也是很多数据结构完爆的题目线段树入门题,但是练分块我们就要写吗 修改还是与之前类似,只不过我们要维护每 ...

  3. 【九度OJ】题目1026:又一版 A+B 解题报告

    [九度OJ]题目1026:又一版 A+B 解题报告 标签(空格分隔): 九度OJ 原题地址:http://ac.jobdu.com/problem.php?pid=1026 题目描述: 输入两个不超过 ...

  4. 【九度OJ】题目1433:FatMouse 解题报告

    [九度OJ]题目1433:FatMouse 解题报告 标签(空格分隔): 九度OJ http://ac.jobdu.com/problem.php?pid=1433 题目描述: FatMouse pr ...

  5. 【九度OJ】题目1177:查找 解题报告

    [九度OJ]题目1177:查找 解题报告 标签(空格分隔): 九度OJ http://ac.jobdu.com/problem.php?pid=1177 题目描述: 读入一组字符串(待操作的),再读入 ...

  6. 【九度OJ】题目1175:打牌 解题报告

    [九度OJ]题目1175:打牌 解题报告 标签(空格分隔): 九度OJ http://ac.jobdu.com/problem.php?pid=1175 题目描述: 牌只有1到9,手里拿着已经排好序的 ...

  7. 【九度OJ】题目1017:还是畅通工程 解题报告

    [九度OJ]题目1017:还是畅通工程 解题报告 标签(空格分隔): 九度OJ 原题地址:http://ac.jobdu.com/problem.php?pid=1017 题目描述: 某省调查乡村交通 ...

  8. 【九度OJ】题目1444:More is better 解题报告

    [九度OJ]题目1444:More is better 解题报告 标签(空格分隔): 九度OJ 原题地址:http://ac.jobdu.com/problem.php?pid=1444 题目描述: ...

  9. 【九度OJ】题目1442:A sequence of numbers 解题报告

    [九度OJ]题目1442:A sequence of numbers 解题报告 标签(空格分隔): 九度OJ 原题地址:http://ac.jobdu.com/problem.php?pid=1442 ...

随机推荐

  1. Bruce Eckel的资源

    1 GitHub的技术博客 2 On Java 8 – Bruce Eckel 3 artima_weblogs - Bruce Eckel 4 back issues 5 eckel-oo-prog ...

  2. 【汇总】Linux常用脚本shell

    [crontab] #每天6:00 执行a.sh00 6 * * * /bin/sh /home/work/rxShell/a.sh #每天3:20 执行a1.sh20 3 * * * /bin/sh ...

  3. Linux 环境下程序不间断运行

    一.背景     在linux命令行中执行程序,程序通常会占用当前终端,如果不启动新的终端就没法执行其他操作.简单可以通过'&'将程序放到后台执行,但是这种方法有个问题就是,一旦连接远程服务器 ...

  4. 移动端页面点击a标签会有半透明的阴影或红色边框的bug

    好久没有更新了,今天来一发 ^_^ 最近在写移动端页面,测试时发现一个a标签的bug:无论是iOS端还是Android端都存在,当点击a标签,会有一个矩形的透明的阴影闪一下(不同的浏览器阴影的颜色还不 ...

  5. makefile讲解

    仅供自己学习使用 一.Makefile介绍 Makefile 或 makefile: 告诉make维护一个大型程序, 该做什么.Makefile说明了组成程序的各模块间的相互 关系及更新模块时必须进行 ...

  6. ACdream 1015 Double Kings 树的重心

    思路:删除根结点,其最大子树的节点最少.求一次树的重心即可. AC代码 #include <cstdio> #include <cmath> #include <ccty ...

  7. java socket 模拟im 即时通讯

    自己想了一下怎么实现,就写了,没有深究是否合理.更多处理没有写下去,例如收件人不在线,应该保存在数据库,等下一次连接的时候刷新map,再把数据发送过去,图片发送也没有做,也没有用json格式 sock ...

  8. Wireshark抓包常见出现错误

    转自这里 1.   tcp out-of-order(tcp有问题) 解答: 1).    应该有很多原因.但是多半是网络拥塞,导致顺序包抵达时间不同,延时太长,或者包丢失,需要重新组合数据单元 因为 ...

  9. Django开发基础----操作数据库

    Django中对数据库的操作是由Models来完成的 Models是什么? 通常,一个Model对应数据库的一张数据表 Django中Models以类的形式出现 它包含了一些基本字段以及数据的一些行为 ...

  10. pyinstaller打包py文件成exe文件时,出现ImportError: No module named 'pefile'错误解决办法!

    首先pyinstaller的安装与使用详见如下链接: 安装完成之后,命令行中输入pyinstaller之后,结果如下: ImportError: No module named 'pefile' 缺少 ...