莫队算法

我不得不佩服一下这个算法.
它把一个非常暴力的算法写得如此优美,让我直接被其中所含的艺术震惊了.
我们来看一下莫队算法的使用范围.

它是一种怎样的算法?

莫队算法是一种用于解决非常奇怪的区间询问修改问题的算法.
这种算法传说中能够解决所有区间的询问修改问题,但好像没有这么万能.

莫队算法的适用范围:
1:该题目仅仅包含询问或者修改非常之简单.
2:该题目支持离线算法.
3:在知道区间[l,r]的答案时,要能在较短时间内推出[l+1,r],[l,r+1],[l-1,r],[l,r-1]这些区间的答案.

所以它适用的范围不算是非常广.但这样足够让我顶礼膜拜它了.

基本写法

根据上面第三条特点,我们可以用类似尺取法的思想维护两个变量 nowl n o w l , nowr n o w r ,让它们不断地在询问中穿梭,并找出每条询问的答案.
你不禁会问,那这样不是还是n^2的复杂度么?不,没这么简单.
最关键的地方就是,我们可以调换询问的顺序,使得两个变量在询问间转换的次数在n*sqrt(n)左右.

这就是本算法需要离线的关键.

我们把序列分块,看看每一个询问的左端点,如果它们所在块相同,则按照r从小到大排序.
不同就按l从小到大排.

这样排序之后为什么转换的复杂度就玄学的变成了 n×n n × n 呢?
ps:搜索曼哈顿距离最小生成树.
1.如果l所属块相同,l跳的长度一定不会超过sqrt(n).
而r单调递增,故r是O(n)的.
2.如果l差一个块,l跳的长度不会超过2*sqrt(n),r不会超过n.
差一个块的情况一共sqrt(n)次,因此这里也是n*sqrt(n).
3.注意当l之间不止差一个块的时候我们已经赚翻了,至少省了一个sqrt(n).这个自己考虑一下吧.
综合以上情况,复杂度就是n*sqrt(n).

你可以直接开始A题了.

bzoj 1878 HH的项链
只有询问,询问[l,r]内出现数字的种数.
首先分析题目,看到利用cnt数组进行桶排,就可以 O(1) O ( 1 ) 进行区间推进.
本题三种条件符合,可以使用莫队.
warning:你什么地方都可以交,千万不要去洛谷!

#include<bits/stdc++.h> //Ithea Myse Valgulious
namespace chtholly{
typedef long long ll;
#define re0 register int
#define rec register char
#define rel register ll
#define gc getchar
#define pc putchar
#define p32 pc(' ')
#define pl puts("")
/*By Citrus*/
inline int read(){
  re0 x=0,f=1;rec c=gc();
  for (;!isdigit(c);c=gc()) f^=c=='-';
  for (;isdigit(c);c=gc()) x=x*10+c-'0';
  return x*(f?1:-1);
  }
inline void read(rel &x){
  x=0;re0 f=1;rec c=gc();
  for (;!isdigit(c);c=gc()) f^=c=='-';
  for (;isdigit(c);c=gc()) x=x*10+c-'0';
  x*=f?1:-1;
  }
template <typename mitsuha>
inline int write(mitsuha x){
  if (!x) return pc(48);
  if (x<0) x=-x,pc('-');
  re0 bit[20],i,p=0;
  for (;x;x/=10) bit[++p]=x%10;
  for (i=p;i;--i) pc(bit[i]+48);
  }
inline char fuhao(){
  rec c=gc();
  for (;isspace(c);c=gc());
  return c;
  }
}using namespace chtholly;
using namespace std;
const int yuzu=5e5;
int n,m,block,cnt[yuzu<<1|31],ans[yuzu|10],a[yuzu|10];
/*block块的大小,cnt每种颜色出现的次数.*/
#define bl(x) ((x)/block)//去看分块
struct query{//将询问保存
int l,r,id;
bool operator <(const query &b) const{
  return bl(l)^bl(b.l)?l<b.l:r<b.r;
  }/*cmp,关键*/
}q[yuzu>>1];

int nowl=1,nowr,nowcnt;
/*nowl,nowr刚才解释过了,nowcnt就是算出来当前区间的答案.*/
void add(int x){if (!cnt[a[x]]++) nowcnt++;}
void del(int x){if (!--cnt[a[x]]) nowcnt--;}
/*
把x这个位置的数据增加到cnt数组里或者从cnt里删掉.
判断一下如果a[x]以前没有出现过,nowcnt就+1.
反之如果a[x]的颜色出现的次数被删为0了,nowcnt就-1.
*/
int main(){
re0 i;
n=read(),block=sqrt(n);
for (i=1;i<=n;++i) a[i]=read();
m=read();
for (i=1;i<=m;++i){
  int l=read(),r=read();
  q[i]=query{l,r,i};
  }
sort(q+1,q+m+1);//保存并排序
for (i=1;i<=m;++i){
  /*你需要理解一下这四个神一般的循环.*/
  for (;nowl<q[i].l;) del(nowl++);
  for (;nowl>q[i].l;) add(--nowl);
  for (;nowr<q[i].r;) add(++nowr);
  for (;nowr>q[i].r;) del(nowr--);
  ans[q[i].id]=nowcnt;//只需轻轻一个赋值.
  }
for (i=1;i<=m;++i) write(ans[i]),pl;//最后输出.
}

bzoj 2038 小Z的袜子
询问在[l,r]区间中随意取出两个数相等的概率,用最简分数表示.
首先思考如何推下个区间的答案.

/*
可以发现,答案的分母是(r-l+1)*(r-l)/2,即随意选出两个数的方法总数.
我们在推下一个数字的时候,看一下前面已经出现了多少个和它相等的数字,加到ans上去.
同理减去的时候也一样.
本题中注意特判概率为0或者l=r.
*/
#include<bits/stdc++.h> //Ithea Myse Valgulious
#define yuri puts("niconiconi")
namespace chtholly{
typedef long long ll;
#define re0 register int
#define rec register char
#define rel register ll
#define gc getchar
#define pc putchar
#define p32 pc(' ')
#define pl puts("")
/*By Citrus*/
inline int read(){
  re0 x=0,f=1;rec c=gc();
  for (;!isdigit(c);c=gc()) f^=c=='-';
  for (;isdigit(c);c=gc()) x=(x<<3)+(x<<1)+(c^'0');
  return f?x:-x;
  }
inline void read(rel &x){
  x=0;re0 f=1;rec c=gc();
  for (;!isdigit(c);c=gc()) f^=c=='-';
  for (;isdigit(c);c=gc()) x=(x<<3)+(x<<1)+(c^'0');
  x=f?x:-x;
  }
template <typename mitsuha>
inline int write(mitsuha x){
  if (!x) return pc(48);
  if (x<0) x=-x,pc('-');
  re0 bit[20],i,p=0;
  for (;x;x/=10) bit[++p]=x%10;
  for (i=p;i;--i) pc(bit[i]+48);
  }
inline char fuhao(){
  rec c=gc();
  for (;isspace(c);c=gc());
  return c;
  }
}using namespace chtholly;
using namespace std;
const int yuzu=5e4;
typedef int fuko[yuzu|10];
int n=read(),m=read(),block=sqrt(n);
fuko a,cnt;
pair<ll,ll> ans[yuzu|10]; 

#define bl(x) ((x)/block)
struct query{
int l,r,id;
bool operator <(const query &b) const{
  return bl(l)^bl(b.l)?l<b.l:r<b.r;
  }
}q[yuzu|10];

int nowl=1,nowr;ll nowcnt;
void add(int x){nowcnt+=cnt[a[x]]++;}
void del(int x){nowcnt-=--cnt[a[x]];}
int main(){
re0 i;
for (i=1;i<=n;++i) a[i]=read();
for (i=1;i<=m;++i){
  int l=read(),r=read();
  q[i]=query{l,r,i};
  }
sort(q+1,q+m+1);
for (i=1;i<=m;++i){
  for (;nowl<q[i].l;) del(nowl++);
  for (;nowl>q[i].l;) add(--nowl);
  for (;nowr<q[i].r;) add(++nowr);
  for (;nowr>q[i].r;) del(nowr--);
  ans[q[i].id]=make_pair(nowcnt,1ll*(q[i].r-q[i].l+1)*(q[i].r-q[i].l)/2);
  }
for (i=1;i<=m;++i){
  if (!ans[i].first) ans[i].second=1;
  ll gcd=__gcd(ans[i].first,ans[i].second);
  write(ans[i].first/gcd),pc('/'),write(ans[i].second/gcd),pl;
  }
}

莫队的核心就是这个神一般的cmp函数以及四个灵魂循环.
带修莫队啊树上莫队啊我都不会,也就到此为止了.
CF上面也有一些比较好的莫队题.
比如说XOR And Favourite Number.
好了,谢谢大家.这个算法太优美了.

莫队算法是一种基于分块的算法,用于解决一些静态区间查询问题,时间复杂度为 $O(n\sqrt{n})$。以下是一个基于Python的莫队算法的示例代码: ```python import math # 定义块的大小 BLOCK_SIZE = 0 # 初始化块的大小 def init_block_size(n): global BLOCK_SIZE BLOCK_SIZE = int(math.sqrt(n)) # 定义查询操作 def query(left, right): pass # 在这里写查询操作的代码 # 定义添加操作 def add(x): pass # 在这里写添加操作的代码 # 定义删除操作 def remove(x): pass # 在这里写删除操作的代码 # 定义莫队算法 def mo_algorithm(n, q, queries): init_block_size(n) queries.sort(key=lambda x: (x[0] // BLOCK_SIZE, x[1])) left, right = 0, -1 for query in queries: while left > query[0]: left -= 1 add(left) while right < query[1]: right += 1 add(right) while left < query[0]: remove(left) left += 1 while right > query[1]: remove(right) right -= 1 query(query[0], query[1]) ``` 在这段代码中,我们首先定义了一个全局变量 `BLOCK_SIZE`,用于表示块的大小。接着,我们定义了三个操作函数 `query()`、`add()` 和 `remove()`,分别用于查询、添加和删除元素。在 `mo_algorithm()` 函数中,我们首先调用 `init_block_size()` 函数初始化块的大小,然后将查询操作按照块的大小和右端点排序,接着使用双指针维护当前查询区间的左右端点,每次移动指针时调用 `add()` 和 `remove()` 函数更新块的状态,最后调用 `query()` 函数进行查询操作。 请注意,这段代码只是一个示例,具体的 `query()`、`add()` 和 `remove()` 函数的实现取决于具体的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值