首页 > 其他分享 >莫队

莫队

时间:2025-01-03 15:38:21浏览次数:1  
标签:int 复杂度 sqrt 端点 莫队 block

前置芝士

分块

算法理解

一种暴力顺序的优化

离线+暴力+分块

通常用于不带修只查询的一类问题

模板题P1972 [SDOI2009] HH的项链

首先使用暴力法求解此题

我们设我们有一个 \(L,R\) 表示现在扫到 \([L,R]\) 区间,对于一个询问 \((l,r)\) 我们暴力的将 \(L->l\),\(R->r\) 然后一格一格的移动,通过维护一个 \(cnt[a[i]]\) 表示 \(a[i]\) 出现的次数来统计答案,这样做因为每次 \(L,R\) 的移动是 \(O(n)\) 的所以总复杂度 \(O(nm)\)

莫队优化:将时间复杂度控制在 \(O(n\sqrt n+m\sqrt n)\)

我们会发现影响时间复杂度的是什么呢? \(L,R\) 的震荡幅度

我们将询问 \((l,r)\) 看成平面上的一个点对

每一次移动的时间复杂度就是相邻两个点的曼哈顿距离,可以发现当我们对于 \(l\),排序后 \(r\) 的振幅达到了 \(O(n)\)

所以我们可不可以限制一下振幅在一个区间内来控制复杂度

想到了分块,我们按照左端点所在块的编号为第一关键字,以右端点为第二关键字排序

考虑一下,左端点的振幅每次被限制在了 \(O(\sqrt n)\) 之内,共有 \(m\) 次操作,左端点移动复杂度为 \(O(m\sqrt n)\)

右端点当左端点在一个块内的顺序时是递增顺序的,也就是说在一个块内右端点移动的复杂度是 \(O(n)\) 的,然后有 \(O(\sqrt n)\) 个块,所以复杂度为 \(O(n\sqrt n)\)

记住右端点不是按块来排序的,因为这样就是一个方格图,无法保证在一个方格中的点的顺序,导致路径增长

还有就是块长不一定是 \(O(\sqrt n)\) 这是根据块长和块的个数带进具体题目中算出来的最优解法,具体题目具体分析

莫队优化:
莫队常数很小,但同时一些卡莫队的题需要卡卡常才能过

有一种优化是奇偶性排序,在块的编号为奇数时我们按照右端点升序排序,偶数时降序,可以优化一点复杂度

总结:莫队就是在暴力的基础上离线优化了一下暴力的枚举顺序,使其控制在 \(O((n+m)\sqrt n)\) 的复杂度内

代码也非常好写:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m,t,block,ans;
int a[N],pos[N],cnt[N],num[N];
struct query{
    int l,r,i;
}ask[N];
void allocate(){
    block=(int)sqrt(n);
    t=n/block;
    if(n%block)  t++;
    for(int i=1;i<=n;i++){
        pos[i]=(i-1)/block+1;
    }
}
bool cmp(query x,query y){
    if(pos[x.l]==pos[y.l]){
        if(pos[x.l]&1)  return x.r<y.r;
        else  return x.r>y.r;
    }
    return pos[x.l]<pos[y.l];
}
void add(int x){
    if(!cnt[a[x]])  ans++;
    cnt[a[x]]++;
}
void del(int x){
    cnt[a[x]]--;
    if(!cnt[a[x]])  ans--;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    allocate();
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        int l,r;
        scanf("%d%d",&l,&r);
        ask[i]={l,r,i};
    }
    sort(ask+1,ask+1+m,cmp);
    int L=1,R=0;
    for(int i=1;i<=m;i++){
        int l=ask[i].l,r=ask[i].r;
        while(L<l)  del(L),L++;
        while(l<L)  L--,add(L);
        while(R<r)  R++,add(R);
        while(r<R)  del(R),R--;
        num[ask[i].i]=ans;
    }
    for(int i=1;i<=m;i++){
        printf("%d\n",num[i]);
    }
}

ps:此题卡莫队,我没有卡过去

P1903 [国家集训队] 数颜色 / 维护队列

带修莫队

莫队本来不支持修改,但通过加一个维度的方式可以使其支持简单修改,我们设 \(t\) 表示在这次查询前经历了 \(t\) 次修改,于是我们就可以维护一次询问 \((l,r,t)\)

然后以l所在的块为第一关键字,r所在的块为第二关键字,t为第三关键字排序

然后我们设块长为 \(B\),有 \(n/B\) 个块,所以 \(t\) 的在一个被 l,r 限制的方格内单向移动复杂度为 \(O(m)\) 有 \(n^2/B^2\) 个方格,复杂度为 \(O(mn^2/B^2)\)

总复杂度为 \(O(mB+mB+mn^2/B^2)\),通过计算当 \(B=n^{ \frac{2}{3}}\) 时最优

代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=133340,M=1e6+5;
int n,m,tot,block,ans,gg;
int a[N],pos[N],cnt[M],num[N];
struct query{
    int l,r;
}ch[N];
struct opera{
    int l,r,t,i;
}ask[N];
void allocate(){
    block=pow(n,0.666);
    for(int i=1;i<=n;i++){
        pos[i]=(i-1)/block+1;
    }
}
bool cmp(opera x,opera y){
    if(pos[x.l]==pos[y.l]){
        if(pos[x.r]==pos[y.r])  return x.t<y.t;
        return pos[x.r]<pos[y.r];
    }
    return pos[x.l]<pos[y.l];
}
void add(int x){
    if(!cnt[a[x]])  ans++;
    cnt[a[x]]++;
}
void del(int x){
    cnt[a[x]]--;
    if(!cnt[a[x]])  ans--;
}
void change(int l,int r,int t){
    if(ch[t].l<=r&&l<=ch[t].l){
        del(ch[t].l);
    }
    swap(a[ch[t].l],ch[t].r);
    if(ch[t].l<=r&&l<=ch[t].l){
        add(ch[t].l);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    allocate();
    for(int i=1;i<=m;i++){
        int l,r;
        char s[1];
        scanf("%s%d%d",s,&l,&r);
        if(s[0]=='Q'){
            tot++;
            ask[tot]={l,r,gg,tot};
        }
        else{
            gg++;
            ch[gg]={l,r};
        }
    }
    sort(ask+1,ask+1+tot,cmp);
    int L=1,R=0,T=0;
    for(int i=1;i<=tot;i++){
        int l=ask[i].l,r=ask[i].r,t=ask[i].t;
        while(L<l)  del(L),L++;
        while(l<L)  L--,add(L);
        while(R<r)  R++,add(R);
        while(r<R)  del(R),R--;
        while(T<t)  T++,change(L,R,T);
        while(t<T)  change(L,R,T),T--;
        num[ask[i].i]=ans;
    }
    for(int i=1;i<=tot;i++){
        printf("%d\n",num[i]);
    }
}

标签:int,复杂度,sqrt,端点,莫队,block
From: https://www.cnblogs.com/zcxnb/p/18650175

相关文章

  • 莫队从入门到人门
    普通莫队详介(P2709小B的询问)普通莫队处理问题的前提是问题可以离线,多次区间查询,\(O(n\sqrtm)\)能过。我们以P2709小B的询问为例,假设当前区间为\([l,r]\),答案为\(ans\),那么\(r\)右移一位时,新加入一个数\(x\),我们只要把\(ans\)加上\(x\)的贡献即可。贡献只需要维......
  • 莫队 学习笔记
    阅前声明题单为方便,题目链接均在洛谷。要用桶的时候请尽量不要使用map或者un_map,会造成不必要的TLE。普通莫队当一个区间问题,可以由\([l,r]\)转移到\([l\pm1,r]\)和\([l,r\pm1]\),且添加、删除都可以已很快的时间完成时,我们可以使用莫队算法。我们先将询问离线下......
  • 莫队1 走上骗分之路
    莫队1走上骗分之路新坑介绍莫队,第一篇是不带修的线性莫队.什么是莫队一种硬往两边扩展(可能是收缩)的玄学算法,是老前辈莫涛老师发明的算法,又因为莫老师进了国家队,所以叫莫队.Google搜索需要搜索"Mo'sAlgo".莫队能解决什么问题很多,只要\([l,r]\)这个区间......
  • 莫队算法(Helped With ChatGPT)
    ​同步我的博客:https://me.mycbxzd.top/archives/32ChatGPT也可能会犯错。请核查重要信息。一、普通莫队算法1.概述普通莫队算法是处理一维区间查询问题的基础版本。主要目标是通过排序和分块,将暴力计算中每次区间操作的重复部分重用,从而优化查询效率。2.适用场景普......
  • 24/11/30 ABC381+莫队+分块+整体二分学习笔记
    [ABC381D]好题。由于题目描述中提到了取出的子序列长度必须是偶数个,所以可以考虑对于原来的\(a\)序列每次进行长度为\(2\)的尺取,这时候不难发现对于开头位置具有两个答案,一个是从\(1\)为开头,一个是从\(2\)为开头,所以写个函数封装一下就好了,类似这种cout<<max(solve(1),solve(2......
  • 【题解】洛谷P5906:【模板】回滚莫队&不删除莫队
    对于一些区间问题,虽然莫队好进行加操作,但并不好进行减操作,所以我们引出了回滚莫队。【模板】回滚莫队&不删除莫队发现我们并不总是知道什么时候取哪些值为最大值,尤其是删操作时,回滚莫队就是只用加操作实现的。我们对询问左端点所在的块排序,相同的话按照右排序,这样对于相同的左......
  • 洛谷P4074糖果公园(带修莫队)
    [WC2013]糖果公园题目描述Candyland有一座糖果公园,公园里不仅有美丽的风景、好玩的游乐项目,还有许多免费糖果的发放点,这引来了许多贪吃的小朋友来糖果公园游玩。糖果公园的结构十分奇特,它由\(n\)个游览点构成,每个游览点都有一个糖果发放处,我们可以依次将游览点编号为\(1\)......
  • P3730 曼哈顿交易(经典题,莫队+值域分块)
    记录这一类很典的题目。题意给定\(n\)个数,\(q\)次询问区间\([l,r]\)内出现次数第\(k\)小的数的出现次数。若区间内不同数的个数小于\(k\)输出-1。\(n,q\le10^5\)。分析发现正常的数据结构以及分块都很难维护这种信息,考虑莫队。莫队加入数时,我们需要更新一个数的......
  • 莫队简单入门
    莫队简单入门补最近一场DIV.4时遇到一道需要求区间众数的题目,完善一下技能树。简介:莫队是一种解决离线区间询问问题的方法。能够在\(O(n\sqrt{n})\)的时间复杂度内求出所有询问的答案。大致流程:1.将所有数据分块。有时需要离散化。2.将所有询问离线,并排序。3.对于区间......
  • 笔记——莫队
    蓝月の笔记——莫队篇简介莫队算法是由莫涛提出的算法。在莫涛提出莫队算法之前,莫队算法已经在Codeforces的高手圈里小范围流传,但是莫涛是第一个对莫队算法进行详细归纳总结的人。莫涛提出莫队算法时,只分析了普通莫队算法,但是经过OIer和ACMer的集体智慧改造,莫队有了多种扩......