• 莫队算法学习笔记


    莫队算法

    原博客地址https://www.cnblogs.com/WAMonster/p/10118934.html

    题目背景

    来一道题,给出一个序列,询问某一段区间中,包含多少种不同的数。

    最简单的解法就是暴力,每次暴力遍历区间,这样的时间复杂度是无法接受的。

    下面来做一个优化:

    每次枚举到一个数值x,增加出现次数时判断一下cnt(x)是否为0,如果为0,则这个数值之前没有出现过,现在出现了,数值数要+1,反之从区间中删除x后也判断一下cnt(x)是否为0,如果为0总数-1。

    然后开两个指针l和r,每次询问不直接枚举,而是移动l和r指针到询问的区间,在统计答案时只在两个指针处加减cnt,然后我们就可以用上一步的办法快速统计答案。

    但是,这个优化在面对区间之间的距离相差很大的时候,即两个指针疲于奔命,从头到尾又从尾到头,这样造成时间复杂度的严重退化,这是我们不能接受的。

    考虑进一步优化。

    我们可以考虑把所有查询区间按左端点排序,从而使左指针最多移动O(n)次,这样右端点又是无序的,整体复杂度在面对极端数据时还是无计可施。

    至此,强大的莫队算法正式开始。

    预处理

    莫对算法操作的核心是分块和排序,我们将大小为n的块分成根号n个块,从1到根号n编号,然后对所有查询区间排序,按左端点在块的编号排序,再按右端点在块的编号排序,排完序后左右指针就以块为单位跳来跳去,看似一顿鸡肋的操作,却优化了一个根号n的时间复杂度!

    具体实现

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=2e5+100;
    int a[maxn];
    int cnt[maxn];
    int belong[maxn];
    int n,m,size,bnum,now,ans[maxn];
    struct query {
        int l,r,id;
    }q[maxn];
    int cmp (query a,query 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);
    } 
    void add(int pos) {
        if(!cnt[a[pos]]) ++now;
        ++cnt[a[pos]];
    }
    void del(int pos) {
        --cnt[a[pos]];
        if(!cnt[a[pos]]) --now;
    }
    int main () {
        cin>>n;
        size=sqrt(n);
        bnum=n/size+(n%size>0);
        for (int i=1;i<=bnum;i++) 
            for (int j=(i-1)*size+1;j<=i*size;j++)
                belong[j]=i;
        for (int i=1;i<=n;i++) cin>>a[i];
        cin>>m;
        for (int i=1;i<=m;i++) {
            cin>>q[i].l>>q[i].r;
            q[i].id=i;
        }
        sort(q+1,q+m+1,cmp);
        int l=1,r=0;
        for (int i=1;i<=m;i++) {
            int ql=q[i].l;
            int qr=q[i].r;
            while (l<ql) now-=!--cnt[a[l++]];
            while (l>ql) now+=!cnt[a[--l]]++;
            while (r<qr) now+=!cnt[a[++r]]++;
            while (r>qr) now-=!--cnt[a[r--]];
            //等价于下面被注释的部分 
            /*
            while(l < ql) del(l++);
            while(l > ql) add(--l);
            while(r < qr) add(++r);
            while(r > qr) del(r--);
            */
            ans[q[i].id] = now;
        }
        for (int i=1;i<=m;i++) cout<<ans[i]<<"
    "; 
    }

    带修改的莫队

    在上一题的基础上,增加单点修改的操作。

    遇到强制在线的待修改题,莫队确实无计可施。

    但是对于允许离线的待修改区间查询,莫队还是可以操作的。

    做法就是把莫队加上一维,变成带修改的莫队。

    具体做法就是,把修改操作编号,可以理解为一个时间轴,查询操作的时间轴沿用最近修改操作的时间轴。计算时定义当前时间轴为t,对于每个查询操作,如果当前的时间轴相对太大了,说明已进行的修改操作比要求的多,就把之前改的改回来,反之往后改。只有当当前区间和查询区间的左右端点、时间轴重合时,才认定区间完全重合,此时的答案就是题目要求的答案。

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=2e5+100;
    int a[maxn];
    int cnt[maxn];
    int ans[maxn];
    int belong[maxn];
    struct query {
        int l,r,t,id;
    }q[maxn];
    struct modify {
        int pos;
        int color;
        int lst;
    }c[maxn];
    int cntq,cntc,n,m,size,bnum;
    int cmp (query a,query b) {
        return (belong[a.l]^belong[b.l])?belong[a.l]<belong[b.l]:((belong[a.r]^belong[b.r])?belong[a.r]<belong[b.r]:a.t<b.t);
    }
    int main () {
        cin>>n>>m;
        size=pow(n,2.0/3.0);//数学证明这样是最优的分块
        bnum=ceil((double)n/size);
        for (int i=1;i<=bnum;i++)
            for (int j=(i-1)*size+1;j<=i*size;j++)
                belong[j]=i;
        for (int i=1;i<=n;i++) cin>>a[i];
        for (int i=1;i<=m;i++) {
            string s;
            cin>>s;
            if (s=="Q") {
                cntq++;
                cin>>q[cntq].l>>q[cntq].r;
                q[cntq].t=cntc;
                q[cntq].id=cntq;
            }
            else {
                cntc++;
                cin>>c[cntc].pos>>c[cntc].color;
            }
        } 
        sort(q+1,q+cntq+1,cmp);
        int l=1,r=0,tt=0,now=0;
        for (int i=1;i<=cntq;i++) {
            int ql=q[i].l,qr=q[i].r,qt=q[i].t;
            while (l<ql) now-=!--cnt[a[l++]];
            while (l>ql) now+=!cnt[a[--l]]++;
            while (r<qr) now+=!cnt[a[++r]]++;
            while (r>qr) now-=!--cnt[a[r--]];
            while (tt<qt) {
                tt++;
                if (ql<=c[tt].pos&&c[tt].pos<=qr) 
                    now-=!--cnt[a[c[tt].pos]]-!cnt[c[tt].color]++;
                swap(a[c[tt].pos],c[tt].color);
            }
            while (tt>qt) {
                if (ql<=c[tt].pos&&c[tt].pos<=qr) 
                    now-=!--cnt[a[c[tt].pos]]-!cnt[c[tt].color]++;
                swap(a[c[tt].pos],c[tt].color);
                tt--;
            }
            ans[q[i].id]=now;
        }
        for (int i=1;i<=cntq;i++) cout<<ans[i]<<"
    ";
    }

    树上莫队

    先解决一个最基本的问题,就是询问节点的子树节点数量,这其实很简单,跑一遍DFS序,记录每个节点入栈和出栈的时间,时间差就是答案。

    把之前的问题放到一颗树上,给出一个n个节点的树,每个节点表示一个整数,问u到v的路径上有多少个不同的整数。

    我们用一遍DFS序求出这个树的欧拉序列,然后就可以转换成序列问题。具体计算时要结合LCA,这里用倍增法求LCA。

    然后到这里我懵b了。之后补。。。

  • 相关阅读:
    单词统计-续
    “帮你APP”团队冲刺10
    软件工程周总结15
    梦断代码阅读笔记03
    个人课程总结
    梦断代码阅读笔记02
    梦断代码阅读笔记01
    软件工程周总结14
    计算最长英语单词链
    软件工程周总结13
  • 原文地址:https://www.cnblogs.com/zhanglichen/p/13213264.html
Copyright © 2020-2023  润新知