【笔记篇】莫队算法(一)

本文介绍了莫队算法,一种用于离线解决区间问题的方法。文章指出,如果能从[L,R]快速推出相邻区间的答案,则可以使用莫队。算法核心是通过二维平面的曼哈顿距离最小生成树,实现O(nlog2n)复杂度,实际操作中可通过分块优化到O(nn‾√)。文中还解释了为何按特定方式排序区间,并提供了代码示例和一道例题来帮助理解。" 123422898,8505379,目标检测模型性能提升技巧,"['目标检测', '计算机视觉', '深度学习', '图像处理', '模型优化']
摘要由CSDN通过智能技术生成

P.S.:这个星期写了一个星期的莫队,现在也差不多理解了,下周该学点别的了(其实是被long long卡得生活不能自理......快要写吐了).

在本文开始之前,先orz莫涛……

莫队算法(Mo’s algorithm),是一种离线解决区间问题的算法.
据说,只要不强制在线,莫队算法能解决所有区间查询问题……

如何判断一个问题可以使用莫队?
如果我们知道[L,R]的答案,便可以O(1)推出[L-1,R] [L,R-1] [L+1,R] [L,R+1]的答案,就可以用莫队做…

莫队算法的基本思想就是,把所有询问离线下来,得到一堆[L,R],我们已经知道上面的东西都可以O(1)求出了,那么我们对于[ L2,R2 ]的答案就可以通过[ L1,R1 ] ,花费 |L2L1|+|R2R1| 的时间求解….

哦 那么我们是不是就可以通过改变询问的顺序来少些重复的转移?
——嗯,没错,二维平面MST(曼哈顿距离最小生成树)!!!
莫队告诉我们,只要按这颗树做,复杂度就不会太高……而我们能在 O(nlog2n) 时间内求出MST…

但这样的编程复杂度岂不是太高了?(MST对我等蒟蒻来说实在是……)
但是不要紧,我们有偷懒的办法——分块的 O(nn) 还是没问题的嘛= =(当然我们假设n,q同级)
(orz 其实用MST做到最后的复杂度也是 O(nn) ,只是常数小了点(别问我为啥,我不会证!!))
我们把左端点的块编号作为第一关键字,把右端点的编号作为第二关键字排序…
然后按排序好的序列推过去就行了orz….

Q:为什么不是按左端点序号为第一关键字,右端点序号为第二关键字排序呢?
A:是为了避免 L 略小于L1略小于 L2 ,但 R1 远小于R远小于 R2 的情况啊……

关于复杂度的证明:
右端点:由于排过序,左端点跨块时变得多,最多变n,有 n 个块所以不超过 O(nn)
左端点:由于按左端点排序,跨两块最多也不超过 2n ,有q个询问,q,n同级所以不超过 O(nn)
而实际上的复杂度应该不到这个最坏复杂度,大概就O(玄学)了…

嗯 差不多就是这样,下面我们看代码……

我们做莫队的时候就是要分析:
我们当前的区间维护到了[L,R],现在遇到了x点…
-如果x∈[L,R]中,肯定要删除(不然就不会用到x点了)
-如果x∉[L,R],我们就要添加
所以我们可以用一个bool数组来维护每个点是不是在区间中(当然这份代码没有这么写)

然后我们就需要一个fix函数来维护……

void fix(int p,int &res)    //res用于维护结果,p表示更新p点
{
    if(ex[p])
    {
        //TODO:删掉p点要维护什么信息
    }
    else
    {
        //TODO:添加p点要维护什么信息
    }
    ex[p]^=1; //处理完p点要将p点存在性取反...
}

处理询问是怎么推过去的呢?

//我们需要定义一个询问的结构体
struct query
{
    int l,r,id; //第id个问题询问[l,r]
};
//我们之前需要一个cmp函数(sort用)
bool cmp(const query &a,const query &b)
{
    if(a.l/blk==b.l/blk) return a.r<b.r; //blk表示分块的大小
    return a.l<b.l;
}

void solve()
{
    sort(q+1,q+m,cmp); //将询问排序
    int l=q[1].l,r=q[1].r-1;int res=0; //[l,r]表示当前处理到的区间,res表示结果
    //开始的时候肯定不会想更新一堆信息所以把区间设为空
    for(int i=1;i<=m;i++)
    {
        int L=q[i].l,R=q[i].r;
        while(l>L) fix(--l,res);
        while(l<L) fix(l++,res);
        while(r>R) fix(r--,res);
        while(r<R) fix(++r,res);
        //这一串记住--l,其他都能推出来,至于为什么,也是很好理解的~
        ans[q[i].id]=res;
    }
}
//基本就是这样咯~

例题? 是莫队在集训队论文中提出的.. bzoj上莫涛版权所有的一道题…
bzoj2038-小z的袜子

化式子什么的我本不想说,但是鉴于实在不是很懂orz…
所以还是要化一下的…

ans=C(cur[colori],2)C(rl+1,2)
=cur[colori]!(cur2)!2!(rl+1)!(rl1)!2!
=cur[colori](cur[colori]1)(rl)(rl+1)
=cur[colori]2colori(rl)(rl+1)
=cur[colori]2+(rl+1)(rl)(rl+1)

应该能看懂吧= = colori 表示i的颜色,cur[i]表示当前颜色为i的节点有多少…

然后根据上面,我们就能得出这样代码:

//此题极限数据50000*50000 一定要开long long
//开long long的时候每一处乘法都要记得强转long long(WA惨的教训)
#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=50005;
typedef long long int64;

int c[N],ex[N];
int64 cur[N];
int n,m,blk;

struct _ans{
    int64 a,b;
}ans[N];    //这题答案是个分数...
struct query{
    int l,r,id;
}q[N]; int ttt;
bool cmp(const query &a,const query &b){
    if(a.l/blk==b.l/blk) return a.r<b.r;
    return a.l<b.l;
}
inline void buildquery(int l,int r,int id){
    q[id].l=l; q[id].r=r; q[id].id=id;
}


inline int getnum(){
    int a=0;char c=getchar();bool f=0;
    for(;(c<'0'||c>'9')&&c!='-';c=getchar());
    if(c=='-') c=getchar(),f=1;
    for(;c>='0'&&c<='9';c=getchar()) a=(a<<1)+(a<<3)+c-'0';
    if(f) return -a; return a;
}
int64 gcd(int64 a,int64 b){
    if(!b) return a;
    return gcd(b,a%b);
}

void fix(int p,int64 &res){
    if(ex[p]){
        res-=(cur[c[p]]<<1)-1;
        //这里是化了一下式子之后简便的位运算版(利用完全平方公式,可以自己推一下)
        cur[c[p]]--;
    }
    else{
        res+=(cur[c[p]]<<1|1); //同上
        cur[c[p]]++;
    }
    ex[p]^=1;
}
void solve(){
    sort(q+1,q+m+1,cmp);
    int l=q[1].l,r=q[1].l-1; int64 res=0;
    for(int i=1;i<=m;i++){
        int L=q[i].l,R=q[i].r,id=q[i].id;
        while(l>L) fix(--l,res);
        while(l<L) fix(l++,res);
        while(r<R) fix(++r,res);
        while(r>R) fix(r--,res);
        if(L==R) ans[id].a=0,ans[id].b=1; //特殊情况特殊处理
        else{
            int64 a=res-(r-l+1),b=(int64)(r-l+1)*(r-l),k=gcd(a,b);
            ans[id].a=a/k,ans[id].b=b/k;
        }
    }
}


int main(){
    n=getnum(),m=getnum(); blk=sqrt(n);
    for(int i=1;i<=n;i++) c[i]=getnum();
    for(int i=1;i<=m;i++){
        int x=getnum(),y=getnum();
        buildquery(x,y,i);
    } solve();
    for(int i=1;i<=m;i++) printf("%lld/%lld\n",ans[i].a,ans[i].b);
}

就这样= = 完结撒花= =

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值