虽然当时文风很2,但是觉得写的蛮好的,就在这里贴一下吧。


最近学了分块(太难想了 \(qwq\) )和莫队(太神奇了 \(0w0\) ),写一个阶段性总结~

分块

总所周知,分块是一种神奇的暴力,用 \(O(n\sqrt{n})\) 的较为优秀的时间复杂度,解决线段树与树状数组不能解决之事

那么,他是怎么做到的呢?

我们找一道模版题

请问线段树和树状数组,您二位又该如何应对?

用线段树?_ Sunmoon _表示,所有线段树做法都可以被题目最后新增的那组hack干掉!

那怎么办?

这时,我们的分块闪亮登场! \(0w0\)

假如树状数组是一颗神奇的树,线段树是一颗高度为 \(log_2n\) 的神奇的树,那分块所产生的块状数组,就是一颗高度仅为3层的神奇的树,如下图:

第一层,他的块长为 \(n\)

第二层,他的块长为 \(\sqrt{n}\)

第三层,对标到每个元素

那么,块分好了,该如何在块上操作呢?

妈妈我会分段!

考虑分成三个部分:

对于两个散块部分,进行修改的时间复杂度不超过 \(O(\sqrt{n})\)

对于一个整块部分,直接将所加的值给整块部分即可,时间复杂度 \(O(\sqrt n)\)

于是,我们支持了 \(O(\sqrt{n})\) 的修改操作(当然这种思路也可以用来做树状数组和线段树的模版题)

那么,如何查询呢?

还是分成原来三个部分

对于两个散块部分,直接暴力看有没有超过c,时间复杂度 \(O(\sqrt n)\)

对于中间的整块部分,我们可以将其提前排序,在查找时二分,时间复杂度 \(O(\sqrt n\ log_2 \sqrt n)\),提前排序时间复杂度为 \(O(nlog_2\sqrt n)\)

所以时间复杂度为 \(O(max(q\sqrt n\ log_2\sqrt n\ ,nlog_2\sqrt n))\),稳稳AC

这就是分块可怕的威力

贴代码~

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,q,l,c[1000005],add[1005];
int k[1005][1005],z[1005][1005],h[1005][1005];
struct zjy{int a,b;}lyh[1005];
int cmp(zjy x,zjy y){return x.a<y.a;}
void sk(int kk,int r){
for(int i=1;i<=r;i++){
lyh[h[kk][i]].a=k[kk][i];
lyh[h[kk][i]].b=h[kk][i];
}
sort(lyh+1,lyh+r+1,cmp);
for(int i=1;i<=r;i++){
k[kk][i]=lyh[i].a;
z[kk][lyh[i].b]=i;
h[kk][i]=lyh[i].b;
}
}
void liu(){
int x,y,w;cin>>x>>y>>w;
int k1=(x-1)/l+2,k2=(y-1)/l;
int k3=(x-1)/l+1,k4=(y-1)/l+1;
if(k3==k4){
for(int i=((x%l)?x%l:l);i<=((y%l)?y%l:l);i++)
k[k3][z[k3][i]]+=w;
sk(k3,(k3>n/l)?n%l:l);return;
}
for(int i=((x%l)?x%l:l);i<=l;i++)
k[k3][z[k3][i]]+=w;
for(int i=1;i<=((y%l)?y%l:l);i++)
k[k4][z[k4][i]]+=w;
sk(k3,l);sk(k4,(k4>n/l)?n%l:l);
for(int i=k1;i<=k2;i++) add[i]+=w;
}
void zhang(){
int x,y,w;cin>>x>>y>>w;
int k1=(x-1)/l+2,k2=(y-1)/l;
int k3=(x-1)/l+1,k4=(y-1)/l+1;
int ans=0;
if(k3==k4){
for(int i=((x%l)?x%l:l);i<=((y%l)?y%l:l);i++)
if(k[k3][z[k3][i]]+add[k3]>=w) ans++;
cout<<ans<<"\n";return;
}
for(int i=((x%l)?x%l:l);i<=l;i++)
if(k[k3][z[k3][i]]+add[k3]>=w) ans++;
for(int i=1;i<=((y%l)?y%l:l);i++)
if(k[k4][z[k4][i]]+add[k4]>=w) ans++;
for(int i=k1;i<=k2;i++){
int d=lower_bound(k[i]+1,k[i]+l+1,w-add[i])-k[i];
ans+=l-d+1;
}
cout<<ans<<"\n";
}
signed main(){
cin>>n>>q;l=sqrt(n);
for(int i=1;i<=n;i++) cin>>c[i];
for(int s=1,e=l;s<=n;s+=l,e=min(e+l,n)){
for(int i=1,j=s;j<=e;i++,j++)
lyh[i].a=c[j],lyh[i].b=i;
sort(lyh+1,lyh+e-s+2,cmp);
for(int i=1;i<=e-s+1;i++){
k[(s-1)/l+1][i]=lyh[i].a;
z[(s-1)/l+1][lyh[i].b]=i;
h[(s-1)/l+1][i]=lyh[i].b;
}
}
while(q--){
char c;cin>>c;
if(c=='M') liu();
else zhang();
}
return 0;
}

事实证明,分块威力远不止于此

再来一题

首先,我们有一个暴力的思路:

记录每个点弹到的下一个点,询问时递推暴跳即可

我们发现,假如按常规思路,这题是绝对做不出来的

那么,分块就成为了一个很好的办法

我们记录两个东西:

1、弹出自己所在的块后到达的第一个点

2、弹出自己所在的块所需的步数

预处理时,可以递推 \(O(n)\) 记录

修改时,只需要修改块内的元素,时间复杂度 \(O(\sqrt n)\)

暴跳顶多调块数次,时间复杂度 \(O(\sqrt n)\)

综上,时间复杂度为 \(O(max(n,m\sqrt n))\)

参考代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,q,l,k[200005];
int nxt[200005],b[200005];
signed main(){
cin>>n;l=sqrt(n);
for(int i=0;i<n;i++) cin>>k[i];
for(int i=n-1;i>-1;i--){
if((i+k[i])/l!=i/l||i+k[i]>=n)
b[i]=1,nxt[i]=i+k[i];
else b[i]=b[i+k[i]]+1,nxt[i]=nxt[i+k[i]];
}
cin>>q;
while(q--){
int o,x,c;cin>>o>>x;
if(o==1){
int ans=0;
while(x<n) ans+=b[x],x=nxt[x];
cout<<ans+b[x]<<"\n";continue;
}
cin>>c;k[x]=c;
for(int i=x;i>=x/l*l;i--){
if((i+k[i])/l!=i/l||i+k[i]>=n)
b[i]=1,nxt[i]=i+k[i];
else b[i]=b[i+k[i]]+1,nxt[i]=nxt[i+k[i]];
}
}
return 0;
}
//怎么说呢……就挺短吧……

至此,我们就了解了分块算法

练习题

loj数列分块9题

莫队

莫队,是由前国家队队长莫涛神犇总结出的一种以分块思想优化排序的离线做法

考虑这个问题

很明显,我们可以对于每个区间,直接 \(O(n^2)\) 暴力

虽说《n方过百万》,但是这看起来也不是个啥好想法

我们有了一种新的想法!

建立两个指针,在数轴上乱跳!如下图:

从这样的状态

变成这样的状态

虽然时间复杂度没有直接优化,但是为我们下一步理解莫队算法打下基础

考虑充分利用可以离线的性质:

将所有询问离线下来,并且以L为关键字排序,这个时候再跳,常数很明显变小了(因为L最多只会跳n下)

可时间复杂度还是没变(R会反复横跳)……

陆游说得好,“山重水复疑无路,柳暗花明又一村”,这时,莫涛神犇为广大OI选手带来了一线曙光。他告诉我们:只要按照分块的思路排序,时间复杂度就可以达到最坏 \(O(n\sqrt n)\)!于是,普通莫队算法诞生了!(我在说啥?)

排序cmp的变化:

int cmp(zjy x,zjy y){
//原来
if(x.l!=y.l) return x.l<y.l;
return x.r<y.r;
//现在
if(x.l/len!=y.l/len) return x.l<y.l;
return x.r<y.r;
}

那么,为什么他的时间复杂度为 \(O(n\sqrt n)\) 呢?

为使过程看起来一目了然,我在描述时可能不会很精确,想要看精准证明,请移步这里

下面开始证明:

考虑在每个块中,R最多跳n次,时间复杂度 \(O(n\sqrt n)\)

L每次跳的量不会超过 \(\sqrt n\) 级别,时间复杂度 \(O(q\sqrt n)\)

当然,这里将询问次数与数列长度视为同阶的。假如两者差异过大,那就要仔细考虑

于是,我们就可以完成对普通莫队算法的逻辑梳理了!

1、排序 2、指针暴跳求答案 3、完结撒花

于是代码就有啦

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,k,b[50005],l=1,r,a,ans;
int p[50005],c[50005],re[50005];
struct zjy{
int s,e,id;
bool operator<(const zjy &x)const{
if(s/a!=x.s/a) return s<x.s;
if(s/a%2) return e<x.e;
return e>x.e;
}
}q[50005];
void add(int x){ans-=c[b[x]]*c[b[x]]-(c[b[x]]+1)*(c[b[x]]+1);c[b[x]]++;}
void del(int x){ans-=c[b[x]]*c[b[x]]-(c[b[x]]-1)*(c[b[x]]-1);c[b[x]]--;}
signed main(){
cin>>n>>m>>k;a=sqrt(n);
for(int i=1;i<=n;i++) cin>>b[i];
for(int i=1;i<=m;i++){
cin>>q[i].s>>q[i].e;
q[i].id=i;
}
sort(q+1,q+m+1);
for(int i=1;i<=m;i++){
while(r<q[i].e) add(++r);
while(r>q[i].e) del(r--);
while(l<q[i].s) del(l++);
while(l>q[i].s) add(--l);
re[q[i].id]=ans;
}
for(int i=1;i<=m;i++) cout<<re[i]<<"\n";
return 0;
}

部分 \(dalao\) 可能会发现一个很重要的问题:本蒟蒻的排序函数变了!

这是为什么呢?

实际上,这是一个略微玄学的优化:奇偶化排序

什么意思呢?

假如按照原来的排序函数,在跳R的时候,每次都要先往右跳,然后一个大跳跳回来,再继续往右跳,但假如我们在R回来的时候,就解决了下一个块的询问,岂不美哉?

于是,我们选择在奇数块向右跳,在偶数块时再向左跳回来。根据某 \(dalao\) 的研究,奇偶化排序通常可以减少30%的时间

那我们再来看下一道题

哪里来的修改?莫队,请问您的修改操作呢?

好吧,如此优雅的暴力很明显也有了致命的缺点:你怎么修改?

有没有暴力一点的解决方式呢?

有!

我们发现,我们刚刚研究的莫队只是在一维上的,我们可否再加一维时间维?

答案是肯定的。我们可以将时间指针T作为排序的第三关键字

假如我们的T小于现在所枚举的时间,我们就让他顺流而上;假如大于,则顺流而下(可以发现,在向上枚举时,原来被修改位置上的数字和修改后的数字地位交换)

那么代码就不难敲了

#include<bits/stdc++.h>
using namespace std;
int w,tms[2][200005],a;
int n,m,k,b[200005],ans;
int c[1000005],re[200005];
struct zjy{
int s,e,t,id;
bool operator<(zjy x){
if(s/a!=x.s/a) return s<x.s;
if(e/a!=x.e/a) return e<x.e;
return t<x.t;
}
}q[200005];//我知道但是zjy永远滴神
void add(int x){if(!c[x]) ans++;c[x]++;}
void del(int x){c[x]--;if(!c[x]) ans--;}
signed main(){
cin>>n>>m;a=(int)pow(n,2.0/3.0);
for(int i=1;i<=n;i++) cin>>b[i];
for(int i=1;i<=m;i++){
char o;int ll,rr;
cin>>o>>ll>>rr;
if(o=='Q') q[++k]={ll,rr,w,k};
else{tms[0][++w]=ll;tms[1][w]=rr;}
}
// sort(q+1,q+k+1);
int l=1,r=0,tt=0;
for(int i=1;i<=k;i++){
while(r<q[i].e) add(b[++r]);
while(r>q[i].e) del(b[r--]);
while(l<q[i].s) del(b[l++]);
while(l>q[i].s) add(b[--l]);
while(tt<q[i].t){
++tt;
if(l<=tms[0][tt]&&tms[0][tt]<=r)
del(b[tms[0][tt]]),add(tms[1][tt]);
swap(b[tms[0][tt]],tms[1][tt]);
}
while(tt<q[i].t){
if(l<=tms[0][tt]&&tms[0][tt]<=r)
del(b[tms[0][tt]]),add(tms[1][tt]);
swap(b[tms[0][tt]],tms[1][tt]);--tt;
}
re[q[i].id]=ans;
}
for(int i=1;i<=k;i++) cout<<re[i]<<"\n";
return 0;
}

虽然代码长了不少,但是基本思路没有变

莫队还有树上莫队等其他分支类型,我会在学明白之后再写一篇详解

练习题1

练习题2

感谢您的观看,希望您不要吝惜您的点赞

(抄自己luogu上的博客)莫队总结的更多相关文章

  1. 用Jekyll在github上写博客——《搭建一个免费的,无限流量的Blog》的注脚

    本来打算买域名,买空间,用wordpress写博客的.后来问了一个师兄,他说他是用github的空间,用Jekyll写博客,说很多人都这么做.于是我就研究了一下. 比较有价值的文章有这么几篇: htt ...

  2. 用Jekyll在github上写博客

    用Jekyll在github上写博客——<搭建一个免费的,无限流量的Blog>的注脚 本来打算买域名,买空间,用wordpress写博客的.后来问了一个师兄,他说他是用github的空间, ...

  3. 第一次在linux上登录博客

    这是我第一次在linux操作系统上登录博客,额,虽然是在X-window上面.好吧,是我太激动了. 这意味着我已经步入linux的世界了,虽然中文输入法不太好用,但是我还是写一下我的心情吧. 从去年的 ...

  4. 在github上搭建博客的问题

    最近想到要建立一个自己的博客,以便记录自己在学习中遇到的问题.发现github免费提供空间,正好可以利用它来搭建自己的博客.毕竟github pages免费空间,不限制流量,每次的博客改动和博客模板的 ...

  5. 本博客不再更新,欢迎访问本人托管在GitHub上的博客:www.wshunli.com

    本博客不再更新. 欢迎访问本人托管在GitHub上的博客:www.wshunli.com

  6. hexo部署到gitcafe上静态博客

    http://zanderzhang.gitcafe.io/2015/09/17/hexo部署到gitcafe上静态博客/ hexo这些事儿,zippera's blog,之类的,这些都说的很清楚了. ...

  7. 大家好,我是一个热爱编程的大二在读生,今天来移植一下CSDN上的博客

    今天开了博客园,将原来再CSDN上的博客移植一下,嘿嘿嘿.

  8. 20155205 《Java程序设计》0510课上实践博客

    20155205 <Java程序设计>0510课上实践博客 一.教材代码检查-p98 未提交成功原因: 一开始在iterm中运行,但是结果出错,没有时间提交了.这个提交其实很简单,没有提交 ...

  9. 利用Octopress在Github上搭建博客及后续问题总汇

    首先贴一下我的新博客地址: http://findingsea.github.io 用Octopress在GitHub上搭建博客已经不是什么新鲜事了,网上的教程也多了去了,大题的方法什么都差不多,这篇 ...

  10. 基于Hexo且在GitHub上搭建博客

    title: 基于Hexo且在GitHub上搭建博客 Welcome to Fofade's Blog! 搭建初衷 大大小小,大学两年,玩了很多,也学了很多. 回首望之,曾经不知道的,现在是知道了,但 ...

随机推荐

  1. MySql 9 in Docker 利用克隆插件搭建主从

    环境说明 Docker Windows 11 MySql 9.1.0 搭建步骤 1. 准备主库 准备一个主库的配置文件 master.cnf [mysqld] server-id=1 log-bin= ...

  2. Shiro简单入门+个人理解(3)

    最后一天,对shiro框架的应用也到此为至了,可能不是太全,但相对于一般的项目,它的作用已经使用了很多了 Shiro的授权: 授权:对用户资源访问的授权(是否允许用户访问此资源) 用户访问系统资源时的 ...

  3. Datadog发布云成本现状报告:83%的容器支出被闲置资源浪费

    原文链接:https://www.datadoghq.com/state-of-cloud-costs/ 编译:CloudPilot AI 尽管灵活多样的云服务为云成本优化提供了诸多机会,但企业在提升 ...

  4. 通过云主机调用API,一键训练部署商品问答模型

    本文分享自华为云社区<[开发者空间实践指导]CodeArts IDE调用API训练商品问答模型>,作者:开发者空间小蜜蜂. 一.案例介绍 在电子商务领域,售前和售后服务是确保客户满意度和提 ...

  5. 【Python】【爬虫】【爬狼】003_获取搜索结果的页数

    # 获取搜索内容的页数 需要的包 import urllib.request # 获取网页源码 import re # 正则表达式,进行文字匹配 from bs4 import BeautifulSo ...

  6. 【前端】【H5 API】实现全屏显示功能

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. Server check fail, please check server xxx ,port 9848 is available

    [1]如果使用docker安装的nacos服务,2.x版本后增加了 grpc 通信并且增加nacos的集群端口上下偏移1000,创建容器时除了8848还需要把7848.9848都暴露出来.如:-p 7 ...

  8. Qt音视频开发29-ffmpeg中x264/x265编码库支持

    一.前言 有了解码当然对应又有编码,编码是信息从一种形式或格式转换为另一种形式的过程也称为计算机编程语言的代码简称编码.用预先规定的方法将文字.数字或其它对象编成数码,或将信息.数据转换成规定的电脉冲 ...

  9. 推荐 5 个 火火火火 的CMS开源.Net项目

    下面推荐5个基于.NetCore开发的CMS开源项目. 一.OrchardCore 基于ASP.NET Core 构建的.模块化和多租户应用程序框架,采用文档数据库,非常高性能,跨平台的系统. 1.跨 ...

  10. OpenMMLab AI实战营 第七课笔记

    OpenMMLab AI实战营 第七课笔记 目录 OpenMMLab AI实战营 第七课笔记 import os import numpy as np from PIL import Image im ...