zoukankan      html  css  js  c++  java
  • 「莫队」学习笔记

    莫队用来离线解决区间询问问题。

    一、不带修莫队

    考虑利用分块的方法对所有询问的区间按照一定的顺序来回答,而不是完全按照输入顺序在线回答,从而使历史信息得到充分利用。这就是莫队相较于暴力要优的地方。

    对$[1,n]$进行分块,每块大小为$B$。按照以下规则来给询问排序:对于两个区间,第一关键字是左端点所在的块。第二关键字是右端点所在的块。第三关键字是右端点的编号。其实在这里这第二关键字是没用的。然而之所以弄得复杂了点是为了更自然地衔接到下文要介绍的“带修莫队”中去。

    当上一个回答的区间是$[l_1,r_1]$,此时要回答的区间是$[l_2,r_2]$时,暴力地移动左右端点,在移动的过程中进行更新或是统计。

    下面来证明时间复杂度:

    左端点依次分布在块中,但块内的左端点不单调。对于块内,每一次最多移动$B$个单位。而移动一次$O(1)$。总共移动$(m)$次。故复杂度为$O(mB)$

    右端点相对于每一个块是单调的。因此处理一个块右端点最多移动$n$个单位。总共$dfrac{n}{B}$个块。因此复杂度为$O(dfrac{n^2}{B})$

    因此复杂度为$O(mB+dfrac{n^2}{B})$


    复杂度相关

    利用对勾函数(均值不等式)可以知道这个函数的最小值为$O(nsqrt{m})$,$size$应当取$sqrt{dfrac{n^2}{m}}$。当$n=m$时,$B$取$sqrt{n}$


     模板(洛谷P2709 小B的询问

    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <cmath>
    #define ll long long
    using namespace std;
    inline int read(){
        int x(0),w(1); char c = getchar();
        while(c^'-' && (c<'0'||c>'9')) c = getchar();
        if(c == '-') w = -1, c = getchar();
        while(c>='0' && c<='9') x = (x<<3) + (x<<1) + c - '0', c = getchar();
        return x*w;
    }
    struct Query{
        int l,r,idx;
        ll ans;
    }q[50010];
    int n,m,K,L,R,sz;
    ll ans;
    int a[50010],cnt[50010];
    inline bool cmp(const Query& A, const Query& B){
        if(A.l/sz != B.l/sz) return A.l/sz < B.l/sz;
        return A.r < B.r; 
    }
    inline bool cmp2(const Query& A, const Query& B){
        return A.idx < B.idx;
    }
    inline void inc(int P){
        ans += (ll)2*(ll)cnt[a[P]]+(ll)1;
        ++cnt[a[P]];
    }
    inline void dec(int P){
        ans -= (ll)2*(ll)cnt[a[P]]-(ll)1;
        --cnt[a[P]];
    }
    int main(){
        n = read(), m = read(), K = read();
        sz = 223;
        for(int i = 1; i <= n; ++i) a[i] = read();
        for(int i = 1; i <= m; ++i){
            q[i].idx = i;
            q[i].l = read(), q[i].r = read();
        }
        sort(q+1,q+m+1,cmp);
        L = R = 1;
        inc(1);
        for(int i = 1; i <= m; ++i){
            while(R < q[i].r) inc(++R);
            while(R > q[i].r) dec(R--);
            while(L < q[i].l) dec(L++);
            while(L > q[i].l) inc(--L);
            q[i].ans = ans;
        }
        sort(q+1,q+m+1,cmp2);
        for(int i = 1; i <= m; ++i){
            printf("%lld
    ",q[i].ans);
        }
        return 0;
    }

     二、带修莫队

    我们对于每一个区间需要三维:左端点,右端点,时间。

    按照以下规则来给询问排序:对于两个区间,第一关键字是左端点所在的块。第二关键字是右端点所在的块。第三关键字是时间。我们发现前两个关键字和不带修莫队的排序方法是一样的。

    关键在于复杂度和块的最优大小证明。

    我们考虑:左端点的移动次数是不变的,是$O(mB)$;右端点现在多出了块内的移动,一样是$O(mB)$,块间移动近似$O(dfrac{n^2}{B})$,因此复杂度的和近似为$O(mB+dfrac{n^2}{B})$。时间指针的复杂度是$O(c(dfrac{n}{B})^2)$。由此,总复杂度为$O(c(dfrac{n}{B})^2+mB+dfrac{n^2}{B})$。

    这个东西分析起来太麻烦啦!设$c=n=m$,复杂度为$y=O(dfrac{n^3}{B^2}+nB+dfrac{n^2}{B})$。我们要求$y$的最小值

    $dfrac{y}{n}=dfrac{n^2}{B^2}+B+dfrac{n}{B}$

    令$t=dfrac{n}{B}$,则可设$dfrac{y}{n}=t^2+dfrac{n}{t}+t$

    由于当$t ightarrow infty$时,$t^2$为$t$的高阶,因此$dfrac{y}{n} approx t^2+dfrac{n}{t}$

    $t^2+dfrac{n}{t}=t^2+dfrac{n}{2t}+dfrac{n}{2t} geq 3sqrt[3]{t^2*dfrac{n}{2t}*dfrac{n}{2t}}=3sqrt[3]{dfrac{n^2}{4}}$

    因此$dfrac{y}{n} geq O(n^{frac{2}{3}})$

    因此$y_{min}=O(n^{frac{5}{3}})$

    而只有当$t^2=dfrac{n}{2t}$时可以满足等号,因此$B$应当取$n^{frac{2}{3}}$


    模板(洛谷P1093 [国家集训队]数颜色 / 维护队列 )

    在修改时有一个很妙的操作,那就是每一次修改交换修改的值和原值,因为下一次修改这个时一定是倒着修改了。

    /*DennyQi 2019*/
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    using namespace std;
    inline int read(){
        int x(0),w(1); char c = getchar();
        while(c^'-' && (c<'0'||c>'9')) c = getchar();
        if(c == '-') w = -1, c = getchar();
        while(c>='0' && c<='9') x = (x<<3) + (x<<1) + c - '0', c = getchar();
        return x*w;
    }
    struct Query{ int l,r,t,idx,ans; }q[50010];
    int n,m,T,x,y,Q,ans,L,R,cur_T,sz;
    int a[50010],b[50010],md[50010],mdc[50010],cnt[1000010];
    char s[10];
    inline bool cmp(const Query& A, const Query& B){
        if(A.l/sz != B.l/sz) return A.l < B.l;
        if(A.r/sz != B.r/sz) return A.r < B.r;
        return A.t < B.t;
    }
    inline bool cmp2(const Query& A, const Query& B){ return A.idx < B.idx; }
    inline void inc(int P){ if(cnt[a[P]]++ == 0) ++ans; }
    inline void dec(int P){ if(--cnt[a[P]] == 0) --ans; }
    inline void modify(int i, int _L, int _R){
        int P = md[i];
        if(_L <= P && P <= _R){
            if(--cnt[a[P]] == 0) --ans;
            swap(a[P],mdc[i]);
            if(cnt[a[P]]++ == 0) ++ans;
        } 
        else swap(a[P],mdc[i]);
    }
    int main(){
        scanf("%d%d",&n,&m);
        sz = 1357;
        for(int i = 1; i <= n; ++i) scanf("%d",a+i);
        for(int i = 1; i <= m; ++i){
            scanf("%s",s);
            if(s[0] == 'Q'){
                ++Q;
                scanf("%d%d",&q[Q].l,&q[Q].r);
                q[Q].idx = Q, q[Q].t = T;
            }
            else{
                ++T;
                scanf("%d%d",&md[T],&mdc[T]);
            }
        }
        sort(q+1,q+Q+1,cmp);
        inc(L=R=1);
        for(int i = 1; i <= Q; ++i){
            while(L < q[i].l) dec(L++);
            while(L > q[i].l) inc(--L);
            while(R < q[i].r) inc(++R);
            while(R > q[i].r) dec(R--);
            while(cur_T < q[i].t) modify(++cur_T,q[i].l,q[i].r);
            while(cur_T > q[i].t) modify(cur_T--,q[i].l,q[i].r);
            q[i].ans = ans;
        }
        sort(q+1,q+Q+1,cmp2);
        for(int i = 1; i <= Q; ++i){
            printf("%d
    ",q[i].ans);
        }
        return 0;
    }

     三、树上莫队

    太难了,咕咕咕

  • 相关阅读:
    京东二面面经(07.17 11:30)
    招银三面手撕代码题(字符串连续子串)
    shein二面(31min)
    京东提前批一面
    两个链表的第一个公共结点
    Java并发机制的底层实现原理
    招银网络(二面07.09)
    黑盒测试与白盒测试
    求1+2+...+n(剑指offer-47)
    第一个只出现一次的字符(剑指offer-34)
  • 原文地址:https://www.cnblogs.com/qixingzhi/p/10904582.html
Copyright © 2011-2022 走看看