莫队算法初探

莫队,是一种算法,是国家队长莫涛发明的orz,

它是来解决什么问题的呢?划重点

我们常常会遇到这样一类题:给你一个\([1,n]\)的序列,每次查询\([l,r]\)的一些信息(例如不同数的个数等),这个时候,我们就可以使用莫队来解决。

注意,莫队是一种离线算法。

我们考虑,当我们知道\([l1,r1]\)的值时,我们要计算出\([l2,r2]\)的值。

我们可以\(O(1)\)地算出\([l1-1,r1],[l1+1,r1],[l1,r1-1],[l1,r1+1]\)的值,那么,我们就可以在\(O(|l2-l1|+|r2-r1|)\)的时间复杂度内完成这次转移,即算出答案。也就是说,我们可以省去这两个询问区间的交集所需要的时间。

而我们现在要做的,就是通过一种特定的排序方式,使得对于一个询问集合\(q\),使得\(\sum_{i=1}^n(|q[i].l-q[i-1].l|+q[i].r-q[i-1].r|)\)达到我们可以接受的值;

我们考虑用分块来优化,以左端点所在的块为第一关键字,右端点的值为第二关键字来排序,这样的时间复杂度就降到了\(O(n\sqrt{n})\)

证明?我也不会啊

根据dllxl的认(ka)真(chang)教(ji)导(qiao),我们在comp函数中可以进行优化(奇偶块优化),即如果是奇数块就右端点升序排列,否则就降序排列:

inline bool comp(Node a,Node b)
{
    return belong[a.l]^belong[b.l] ? belong[a.l] < belong[b.l] : belong[a.l]&1 ? a.r<b.r : a.r>b.r;
}

这样会快很多,还有就是block的大小跟代码速度有巨大关系,根据dllxl等人的不懈努力,确定\(block=n/(m*2/3)\)时,是最快的(目前);

讲讲题吧:

小Z的袜子

我们考虑对于一个区间\([l,r]\),设\(cnt[x]\)为此区间中颜色为x的个数,则选到相同袜子的情况有\(\sum C_{cnt[x]}^2\)种,而任意选两个,一共有\(C_{r-l+1}^2\)种选法,两者相除即为答案。

由公式

\(C_n^2=\frac{n!}{2!*(n-2)!}=\frac{n*(n-1)}{2}\)并整理得

设\(len=r-l+1\),则答案为

\(\frac{\sum cnt[x]^2-\sum cnt[x]}{len\times(len-1)}\)

\(=\frac{\sum cnt[x]^2-len}{len\times(len-1)}\)

而我们要维护的,就是\(\sum cnt[x]^2(x\in col(l,r))\);

用莫队:

考虑转移的时候,如果加入一个元素x,则\(ans+=cnt[x]*2+1,cnt[x]++\)

(因为\((cnt[x]+1)^2=cnt[x]^2+2*cnt[x]+1\))

同理,当删除一个元素x,则\(ans-=cnt[x]*2-1,cnt[x]--\)(因为\((cnt[x]-1)^2=cnt[x]^2-2*cnt[x]+1\))

我们可以写两个函数:

inline void add(int x) {ans+=cnt[x]*2+1,cnt[x]++;}
inline void del(int x) {ans-=cnt[x]*2-1,cnt[x]--;}

然后,我们每转移的时候:

while(q[i].l>l) del(a[l++]);
while(q[i].l<l) add(a[--l]);
while(q[i].r<r) del(a[r--]);
while(q[i].r>r) add(a[++r]);

再统计一下答案,约个分就OK了

注意:先处理第一组询问,\(l==r\)时要特判

然后?然后好像就没了,完结撒花

上代码:

#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxN=1e5 + 100;
struct Node
{
    int l,r,id;
}q[maxN+1];
int belong[maxN+1],block;
int a[maxN+1],n,m,l,r;
long long res[maxN+1][2],ans,cnt[maxN+1];
inline int read()
{
    int num=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch==‘-‘) f=-1; ch=getchar();}
    while(isdigit(ch)) num=(num<<3)+(num<<1)+(ch^48),ch=getchar();
    return num*f;
}
inline bool comp(Node a,Node b)
{
    return belong[a.l]^belong[b.l] ? belong[a.l]<belong[b.l] : belong[a.l]&1 ? a.r<b.r : a.r>b.r;
}
inline void add(int x) {ans+=2*cnt[x]+1; cnt[x]++;}
inline void del(int x) {ans-=2*cnt[x]-1; cnt[x]--;}
inline long long gcd(long long x,long long y,long long r)
{
    if(!r) return y;
    return gcd(y,r,y%r);
}
inline void work(int x)
{
    long long len=q[x].r-q[x].l+1,tmp=ans-len;
    if(!tmp) {res[q[x].id][0]=0,res[q[x].id][1]=1; return;}
    len=len*(len-1);
    long long g=gcd(tmp,len,tmp%len);
    res[q[x].id][0]=tmp/g,res[q[x].id][1]=len/g;
}
int main()
{
    n=read(),m=read(); block=n/sqrt(m*2/3);
    for(int i=1;i<=n;i++) a[i]=read(),belong[i]=(i-1)/block+1;
    for(int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].id=i;
    sort(q+1,q+m+1,comp);
    for(int i=q[1].l;i<=q[1].r;i++) add(a[i]);
    work(1);
    l=q[1].l,r=q[1].r;
    for(int i=2;i<=m;i++)
    {
        while(q[i].l>l) del(a[l++]);
        while(q[i].l<l) add(a[--l]);
        while(q[i].r<r) del(a[r--]);
        while(q[i].r>r) add(a[++r]);
        work(i);
    }
    for(int i=1;i<=m;i++) printf("%lld/%lld\n",res[i][0],res[i][1]);
    return 0;
}

原文地址:https://www.cnblogs.com/cmwqf/p/10225283.html

时间: 2024-11-09 11:44:32

莫队算法初探的相关文章

【算法】莫队算法初探

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

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

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

莫队算法

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]. 详细的莫队可以百度学一

(普通的)莫队算法简单介绍

莫队算法(由莫涛发明的)是一种离线的暴力算法(至少我这么认为).使用莫队算法的条件是,知道一个区间[l, r]的结果,那么也可以快速知道[l + 1, r],[l - 1, r], [l, r - 1], [l, r + 1]这四个区间的结果.于是可以想到,直接通过这样转移来解决一些问题.当然有些出题人机智,故意卡这种暴力,让你从头跑到尾然后从尾跑到头,于是时间复杂度高达O(n2) 而莫队算法就是通过改变处理询问的顺序来降低时间复杂度. 比如说现在知道一个区间[l1, r1],又要转移到[l2,

莫队算法良心讲解

问题:有n个数组成一个序列,有m个形如询问L, R的询问,每次询问需要回答区间内至少出现2次的数有哪些. 朴素的解法需要读取O(nm)次数.如果使用STL的Map来保存出现的次数,每次需要O(nmlogn)的复杂度.有没有更快的方法呢? 注意到询问并没有强制在线,因此我们可以使用离线方法.注意到一点,如果我们有计算完[L, R]的map,那么[L - 1, R].[L + 1, R].[L, R - 1].[L, R + 1]都能够在O(logn)的复杂度得出.是否能安排适当的询问顺序,使得每次

清橙A1206 小Z的袜子(莫队算法)

A1206. 小Z的袜子 时间限制:1.0s   内存限制:512.0MB 总提交次数:744   AC次数:210   平均分:44.44 将本题分享到: 查看未格式化的试题   提交   试题讨论 试题来源 2010中国国家集训队命题答辩 问题描述 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿.终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命-- 具体来说,小Z把这N只袜子从1到N编号,然后从编号L到R(L 尽管小Z并不在意两只袜子是不是