初探莫队

2019年的某月某天某神仙讲了莫队,但是我一直咕咕咕到了2020年

什么是莫队

莫队是一种优雅的暴力,也是用来完成区间询问的。普通莫队复杂度\(O(n \sqrt n)\)。一种十分优美的离线做法

前置芝士

0.拥有脑子

1.\(STL\)中\(sort\)的\(cmp\)

2.看/写超长的三目运算符的耐心

3.分块的思想

当然了如果不会这些也没有关系,下面还会再讲的

正片开始

先来一道卡了莫队的莫队模板题
HH的项链


最最暴力的做法:显然我们可以对每个询问暴力跑一次,但显然\(O(n^2)\)跑不起。
在上面的暴力中,我们浪费了大量之前遍历过的区间的信息,现在考虑利用起这些信息。我们可以设置两个指针\(l,r\)表示当前所处的区间左端点和右端点。初始化\(l=1,r=0\)。(为了避免某些神奇的\(RE\))如果\(l,r\)不与询问区间的端点重合,就不断的跳\(l,r\)来更新答案。如果\(l\)在左端点右边,就不断向左跳,同时将\(l\)跳到的数统计进答案中,直到与左端点重合。如果\(l\)在左端点左边,就不断往右跳,同时将曾经待过的点从答案中删掉。对于这个题来说,可以用\(cnt[x]\)表示\(x\)这个数出现的次数,如果某次增加时,\(cnt[x]==0\),\(ans\)就\(+1\),如果某次删除时发现删完后\(cnt[x]==0\),\(ans\)就\(-1\)。

我们发现上面这个优化对于这种图来说效率极高:

其中\(x_i\)表示第\(i\)次询问对应的区间

但是对于这种数据来说就凉了

上面的优化方式在\(x_4\)里面不断得左右来回跳,导致浪费了大量的时间。

所以我们不妨把询问的区间进行排序。这样做就必须离线了。怎么排序呢?按照左端点单调递增?显然右端点无序会让这个优化只增加\(O(nlogn)\)的排序复杂度。这时候,就要用到分块思想了。

我们把整个序列分成\(\sqrt n\)个块,按照\(l\)所在的块升序排列为第一关键字,\(r\)升序排列为第二关键字排序。感觉好像没有什么用诶?但确实是个极大的优化至于为什么我也不知道

代码如下:

struct Q{
   int l,r,id,nub;//nub表示左端点在哪个块里
}qry[200009];
bool cmp(Q a,Q b)
{
    if(a.nub!=b.nub) return a.nub<b.nub;
    return a.r<b.r;
}

当然卡常一点也可以写成这样:

bool cmp(Q a,Q b)
{
   return (a.nub^b.nub)?a.nub<b.nub:a.r<b.r;
}

过莫队板子的必备技能是卡常
这样基本的莫队就撒花完结了。

因为这道板子题卡了莫队,所以请走数据弱化版D_QUERY
板子题代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline ll read()
{
    char ch=getchar();
    ll x=0;bool f=0;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<3)+(x<<1)+(ch^48);
        ch=getchar();
    }
    return f?-x:x;
}
int n,q,a[30009],ans[200009],cnt[1000009],all;
struct Q{
    int l,r,nub,id;
}qry[200009];
bool cmp(Q a,Q b)
{
    if(a.nub!=b.nub) return a.nub<b.nub;
    return a.r<b.r; //由于这题不卡常所以就没有卡
}
void add(int k)
{
    if(!cnt[a[k]]) all++;
    cnt[a[k]]++;
}
void del(int k)
{
    cnt[a[k]]--;
    if(!cnt[a[k]]) all--;
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++)
     a[i]=read();
    q=read();
    int sn=sqrt(n);
    for(int i=1;i<=q;i++)
    {
        qry[i].id=i;qry[i].l=read();qry[i].r=read();
        qry[i].nub=qry[i].l/sn+1;
        if(qry[i].l%sn==0) qry[i].nub--;
    }
    sort(qry+1,qry+1+q,cmp);
    int l=1,r=0;
    for(int i=1;i<=q;i++)
    {
        while(r<qry[i].r) add(++r);
        while(r>qry[i].r) del(r--);
        while(l<qry[i].l) del(l++);
        while(l>qry[i].l) add(--l);
        ans[qry[i].id]=all;
    }
    for(int i=1;i<=q;i++)
     printf("%d\n",ans[i]);
}

莫队的玄学优化

奇偶性排序

虽然上面的排序方法优化很大,但是能不能更快一点以便卡过毒瘤题呢?
方法当然是有的辣。
我们先来康康按照上面的排序方法会排出来个啥

这是一堆询问区间以及并不优美的块的分界线
排序后:

这样左端点跳动幅度不大,右端点在同一个块内也是递增的。但是当\(r\)从一个块跳到下一个块的时候发现有时候会倒退回来好多,然后又要重新向右跳。是不是有点浪费?所以奇偶性排序就是在奇数块内右端点按升序排序,偶数块内右端点按降序排序,这样右端点在往回跳的时候就能顺带跳完偶数块的询问。理论上能快一半

上面的按照奇偶性排序:

手动模拟\(r\)的跳跃发现真的优化了不少
代码:

bool cmp(Q a,Q b)
{
  return (a.nub^b.nub)?(a.nub<b.nub):((a.nub%2)?a.r<b.r:a.r>b.r);
}
乱七八糟系列

\(pragma\ GCC\ optimize(2),pragma\ GCC\ optimize (3),register\),快读快输,\(inline\),把\(for\)里的\(i++\)换成\(++i\),用三目运算符代替blabla(待会卡带修莫队板子要用)

带修莫队

现在毒瘤出题人要求修改,怎么办呢?
就像这道题:数颜色


在很久很久以前,这道题是可以拿树套树卡过的你甚至只用去搞搞set,但是现在拿带修莫队都要吸氧了\(qaq\)

好了我们回到正题。
我们只需要在原来的莫队的基础上再加一维时间轴。将询问和修改分开存储。如果这次询问的时间在当前时间之后,就不断修改,直到时间相同。如果询问时间在当前时间之前,就再改回去,我们可以用\(swap\)做到,从而不用再开变量维护原来的值。

当然了,排序方式也有变化。这次我们按照\(l\)所在的块为第一关键字,\(r\)所在的块为第二关键字,时间为第三关键字进行排序。同时,奇偶性排序也不再适用。
排序:

bool cmp(Q a,Q b)
{
    return (bl[a.l]^bl[b.l])?bl[a.l]<bl[b.l]:((bl[a.r]^bl[b.r])?bl[a.r]<bl[b.r]:a.ti<b.ti);
}

注意块的大小会对复杂度有着极大的影响。据大佬证明当块的大小为\(n^{\frac{3}{4}}\)时,复杂度最优。但是我不会证。

由于这个题窝太菜了,不拿\(O_2\)实在是卡不过去,所以只好放上一份加\(O_2\)的代码了

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read()
{
    char ch=getchar();
    int x=0;bool f=0;
    while(ch<'0'||ch>'9')
    {
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<3)+(x<<1)+(ch^48);
        ch=getchar();
    }
    return f?-x:x;
}
int n,k,q,a[133339],bl[133339],ans[133339],cnt[1000009];
int all;
struct Q{
    int l,r,ti,id;
}qry[133339];
struct M{
    int p;
    int col;
}mdi[133339];
bool cmp(Q a,Q b)
{
    return (bl[a.l]^bl[b.l])?bl[a.l]<bl[b.l]:((bl[a.r]^bl[b.r])?bl[a.r]<bl[b.r]:a.ti<b.ti);
}
inline void add(int k)
{
    if(!cnt[a[k]]) all++;
    cnt[a[k]]++;
}
inline void del(int k)
{
    cnt[a[k]]--;
    if(!cnt[a[k]]) all--;
}
inline void modi(int i,int ti)
{
    if(mdi[ti].p>=qry[i].l&&mdi[ti].p<=qry[i].r)
    {
        int x=--cnt[a[mdi[ti].p]];
        int y=++cnt[mdi[ti].col];
        if(!x) all--;
        if(y==1) all++;
    }
    swap(a[mdi[ti].p],mdi[ti].col);
}
int main()
{
    n=read();q=read();
    for(int i=1;i<=n;i++)
     a[i]=read();
    int qc=0,mc=0;
    for(int i=1;i<=q;i++)
    {
        char k=getchar();
        while(k!='Q'&&k!='R') k=getchar();
        if(k=='Q')
        {
            qry[++qc].l=read();qry[qc].r=read();
            qry[qc].ti=mc;qry[qc].id=qc;
        }
        if(k=='R')
        {
            mdi[++mc].p=read();mdi[mc].col=read();
        }
    }
    int sn=pow(n,3.0/4.0);
    for(int i=1;i<=n;i++)
    {
        bl[i]=(i-1)/sn+1;
    }
    sort(qry+1,qry+1+qc,cmp);
    int now=0,l=1,r=0;
    for(int i=1;i<=qc;i++)
    {
        while(r<qry[i].r) add(++r);
        while(r>qry[i].r) del(r--);
        while(l<qry[i].l) del(l++);
        while(l>qry[i].l) add(--l);
        while(now<qry[i].ti) modi(i,++now);
        while(now>qry[i].ti) modi(i,now--);
        ans[qry[i].id]=all;
    }
    for(int i=1;i<=qc;i++)
     printf("%d\n",ans[i]);
}
 

原文地址:https://www.cnblogs.com/lcez56jsy/p/12120859.html

时间: 2024-10-14 10:38:42

初探莫队的相关文章

HDU_1175_莫队+逆元

http://acm.hdu.edu.cn/showproblem.php?pid=5145 初探莫队,就是离线排序后的暴力,但是效率大大提高,中间要除法取模,所以用到了逆元. #include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<cmath> #define LL long long #define MOD 1000000007 us

莫队算法&amp;#183;初探总结

莫队算法分那么几类: 普通序列 带修改 树上 回滚 支持在线 其实上述的类型还可以组合起来(非常的毒瘤). 个人理解莫队算法的精髓在于如何利用暴力将答案再合理的时间和空间内跑出来.说白了: \[莫队算法=一种很牛逼的自定义排序+分块处理+暴力 \] 首先要理解自定义排序,这个排序之后整个序列可以最快地处理所有的询问(这里暂时不谈第五类问题(支持在线),这里认为莫队是只能离线处理问题的,必须先把所有的问题都离线下来).怎么为之快,快要看左端点移动的总距离+右端点移动的总距离最小.那么一般用块的奇偶

【算法】莫队算法初探

[算法介绍] 莫队算法是用于离线处理处理区间问题的一类算法,非常易于理解和上手,应用面十分广泛,甚至还可以在树上进行操作. 当我们得到$[L,R]$的答案之后,如果能够以较低的复杂度扩展得到$[L-1,R],[L+1,R],[L,R-1],[L,R+1]$的答案,我们就可以使用莫队算法,通常这个扩展的复杂度是$O(1)$或$O(logn)$. 如果我们对于每个询问都暴力移动左右端点,那么复杂度肯定是$O(n^2)$的,而莫队算法的精髓就在于结合了分块的思想. 设扩展一次的复杂度为$O(f(n))

莫队算法初探

莫队,是一种算法,是国家队长莫涛发明的orz, 它是来解决什么问题的呢?划重点 我们常常会遇到这样一类题:给你一个\([1,n]\)的序列,每次查询\([l,r]\)的一些信息(例如不同数的个数等),这个时候,我们就可以使用莫队来解决. 注意,莫队是一种离线算法. 我们考虑,当我们知道\([l1,r1]\)的值时,我们要计算出\([l2,r2]\)的值. 我们可以\(O(1)\)地算出\([l1-1,r1],[l1+1,r1],[l1,r1-1],[l1,r1+1]\)的值,那么,我们就可以在\

(莫队算法)CodeForces - 617E XOR and Favorite Number

题意: 长度为n的数列,m次询问,还有一个k.每次询问询问询问从数列的L到R内有多少个连续子序列异或起来等于k. 分析: 因为事先知道这题可以用莫队写,就正好用这题练习莫队. 预处理每个前缀异或和. 然后莫队按分块排序后,不断更新,用一个数组cnt[]记录当前L到R前缀和的数量. R向右拉,新增的数量就是cnt[pre^k],pre表示当前这个R位置的前缀异或和,然后更新一下cnt. 其他的也类似. 算是一个比较好的入门题. 代码: 1 #include <cstdio> 2 #include

莫队算法

Beautiful Girl 题意 给定一个长度为 n 的序列 a[1], a[2], ..., a[n] . m 组询问 (l, r, K) , 求区间 [l, r] 去除重复的数之后的第 K 小. n, m <= 100000 . 分析 莫队算法 + 值域分块. 1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <cctype> 5 #include &

BZOJ4241 历史研究 莫队算法 堆

欢迎访问~原文出处--博客园-zhouzhendong&AK 去博客园看该题解 题目 Description IOI国历史研究的第一人--JOI教授,最近获得了一份被认为是古代IOI国的住民写下的日记.JOI教授为了通过这份日记来研究古代IOI国的生活,开始着手调查日记中记载的事件. 日记中记录了连续N天发生的时间,大约每天发生一件. 事件有种类之分.第i天(1<=i<=N)发生的事件的种类用一个整数Xi表示,Xi越大,事件的规模就越大. JOI教授决定用如下的方法分析这些日记: 1.

CodeForces - 86D 莫队算法

http://codeforces.com/problemset/problem/86/D 莫队算法就是调整查询的顺序,然后暴力求解. 每回可以通过现有区间解ans(l,r)得到区间(l+1,r),(l-1,r),(l,r+1),(l,r-1)的区间解. 调整方式http://blog.csdn.net/bossup/article/details/39236275 这题比那个还要简单,查询的是K^2*Z,很清楚就是莫队算法,然而做的时候没有学过,回来补题补到 关键是我一直没明白为什么重载小于号

codeforces 617 E. XOR and Favorite Number(莫队算法)

题目链接:http://codeforces.com/problemset/problem/617/E 题目: 给你a1 a2 a3 ··· an 个数,m次询问:在[L, R] 里面又多少中 [l, r] 使得 al xor al+1 xor ··· ar 为 k. 题解: 本题只有区间查询没有区间修改,而且数据量不大(10w),所以可以用离线的方法解决. 使用莫队算法来解决,就需要O(1)的修改[L, R+1] .[L, R-1].[L+1, R].[L-1, R]. 详细的莫队可以百度学一