带修改莫队算法
update in 2017.12.24:
以前写的≈shit,实在看不下去了,重写一遍
pre
很早之前就学习了莫队算法。
老师讲课的时候就提到过带修改莫队在线莫队树上莫队树上带修改莫队……但是一直都没有做到过有关的题,
今天有幸做了一道裸的带修改莫队的题,
那就来分享一下自己的经验
带修改的莫队
首先我们要知道,普通的莫队算法是不资瓷修改操作的,
不过后人对莫队算法加以改进
发明了资瓷修改的莫队算法
思路:
在进行修改操作的时候,修改操作是会对答案产生影响的(废话)
那么我们如何避免修改操作带来的影响呢?
首先我们需要把查询操作和修改操作分别记录下来。
在记录查询操作的时候,需要增加一个变量来记录离本次查询最近的修改的位置
然后套上莫队的板子,与普通莫队不一样的是,你需要用一个变量记录当前已经进行了几次修改
对于查询操作,如果当前改的比本次查询需要改的少,就改过去
反之如果改多了就改回来
说的听绕口的
比如,我们现在已经进行了3次修改,本次查询是在第5次修改之后,那我们就执行第4,5次修改
这样就可以避免修改操作对答案产生的影响了
code
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int MAXN=2*1e6+10;
inline int read()
{
char c=getchar();int x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0',c=getchar();}
return x*f;
}
int N,M;
int a[MAXN],where[MAXN];
struct Query
{
int x,y,pre,id;
}Q[MAXN];
int Qnum=0;
struct Change
{
int pos,val;
}C[MAXN];
int Cnum=0;
int color[MAXN],ans=0,base,out[MAXN];
int comp(const Query &a,const Query &b)
{
if(a.x!=b.x) return where[a.x]<where[b.x];
if(a.y!=b.y) return where[a.y]<where[b.y];
return a.pre<b.pre;
}
void Add(int val)
{
if(++color[val]==1) ans++;
}
void Delet(int val)
{
if(--color[val]==0) ans--;
}
void Work(int now,int i)
{
if(C[now].pos>=Q[i].x&&C[now].pos<=Q[i].y)//注意:只有修改在查询的区间内才会对查询的结果产生影响
{
if( --color[a[C[now].pos]] == 0 ) ans--;
if( ++color[C[now].val] == 1) ans++;
}
swap(C[now].val,a[C[now].pos]);
//这里有个很巧妙的操作
//对于一个操作,下一次需要为的颜色是本次被改变的颜色
//比如,我把颜色3改为了7,那么再进行这次修改的时候就是把7改为3
//所以直接交换两种颜色就好
}
void MoQueue()
{
int l=1,r=0,now=0;
for(int i=1;i<=Qnum;i++)
{
while(l<Q[i].x) Delet(a[l++]);
while(l>Q[i].x) Add(a[--l]);
while(r<Q[i].y) Add(a[++r]);
while(r>Q[i].y) Delet(a[r--]);//以上四句为莫队模板
while(now<Q[i].pre) Work(++now,i);//改少了,改过去
while(now>Q[i].pre) Work(now--,i);//改多了,改回来
out[Q[i].id]=ans;//统计答案
}
for(int i=1;i<=Qnum;i++)
printf("%dn",out[i]);
}
int main()
{
N=read();M=read();
base=sqrt(N);
for(int i=1;i<=N;i++) a[i]=read(),where[i]=(i-1)/base+1;
while(M--)
{
char opt[5];
scanf("%s",opt);
if(opt[0]=='Q')
{
Q[++Qnum].x=read();
Q[Qnum].y=read();
Q[Qnum].pre=Cnum;//别忘了记录最近的修改位置
Q[Qnum].id=Qnum;
}
else if(opt[0]=='R')
{
C[++Cnum].pos=read();
C[Cnum].val=read();
}
}
sort(Q+1,Q+Qnum+1,comp);//玄学排序
MoQueue();
return 0;
}
边界问题
由于刚开始的now=0
所以需要先增后执行
撤销的时候需要将最后一次的修改撤销掉
所以先执行后减
复杂度证明
普通莫队时间复杂度为Oleft( ntimes sqrt {n}right)
证明:
当我们第i个询问转移的第i+1个询问时
- 如果第i个询问区间和第i+1个询问区间的左端点所在块的编号相同,那么左端点的移动不会超过sqrt{n}。
也就是说,左端点一直在块内移动的总复杂度为O(n*sqrt{n})(因为左端点最多转移n次,减去左端点跨越块的部分,不足n)
同时由于右端点升序,那么若l,l+1,,,r-1,r的询问区间左端点所在块的编号相等,那么右端点的移动不会超过n次。有一位有sqrt{n}个块,
所以这一部分的复杂度是O(n*sqrt{n})的。
- 考虑左端点跨越块的情况,每次跨越最大是O(2*sqrt{n})那么左端点跨越块的复杂度O(n*sqrt{n})的。
又在这个期间,每次左端点跨越的时候,右端点可能要移动O(n)次,一共左端点跨越sqrt{n}个块,所以右端点复杂度是O(n*sqrt{n})的。
综上莫队算法的排序保证时间复杂度是O(n*sqrt{n})的
带修改莫队算法的时间复杂度证明
以下内容借鉴自洛谷题解
原版莫队是将区间(l,r)视为点(l,r),带修改的即加一维时间轴(l,r,t)
对于t轴的移动可以保存每次修改,如果修改在(l,r)间则更新
分块方法可以参照原版莫队,先将l分块,再讲r分块,同一块的按t排序
块大小为sqrt [3] {nt}可以达到最快的理论复杂度Oleft( sqrt [3] {n^{4}t}right),证明如下
设分块大小为a,莫队算法时间复杂度主要为t轴移动,同r块l,r移动,l块间的r移动三部分
t轴移动的复杂度为Oleft( dfrac {n^{2}t}{a^{2}}right),同r块l,r移动复杂度为0left( naright),l块间的r移动复杂度为0left( dfrac {n}{a}right)
三个函数max的最小值当a为sqrt [3] {nt}取得,为Oleft( sqrt [3] {n^{4}t}right)
- 机器学习工具:Python 和 Numpy入门
- 机器学习决策树:sklearn分类和回归
- 机器学习决策树:提炼出分类器算法
- 机器学习:XGBoost 安装及实战应用
- Ryu:如何在LLDP中添加自定义LLDPDU
- 机器学习|kaggle数据挖掘和求解的基本步骤
- mybatis_generator_逆向工程的使用笔记
- 纳税服务系统总结
- 纳税服务系统一(用户模块)【简单增删改查、日期组件、上传和修改头像】
- 纳税服务系统三(优化处理)【异常处理、抽取BaseAction】
- 机器学习|快速排序思想求topk
- 纳税服务系统二(用户模块)【POI、用户唯一性校验】
- 纳税服务系统四(角色模块)【角色与权限、角色与用户】
- 纳税服务系统五(登陆与系统拦截)【配置系统、子系统首页、登陆与拦截】
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- prometheus学习笔记(3)-使用exporter监控mysql
- elasticsearch query里面的slop选项
- 彻底明白Android设计模式—(动态)代理模式
- elastic search 如何将yellow 状态变为green健康状态
- All clients has disconnected from. You can graceful shutdown now., dubbo version: , current host
- Failed to instantiate org.mybatis.spring.SqlSessionTemplate Constructor threw exception
- 护网之Linux应急处理操作手册
- 对新版安全狗学习
- redis key的删除策略及LRU的实现
- 护网Linux应急处置操作手册-Tools篇
- 加密,编码三问
- 我是怎么挖掘yii2反序列化0day的
- yii2反序列化后续
- 线程基础三问——猫眼真题
- V8引擎对Array.prototype.push的源码实现