• 莫队学习笔记


    首先,贴上大佬的讲解WAMonster

    然后大佬的卡常技巧一开始并没有看懂,经过多处查找。

    下面的函数

    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);
    }

    等同于

    int cmp(query a, query b) {//对于左端点在同一奇数块的区间,右端点按升序排列,反之降序
        return (belong[a.l]==belong[b.l]) ? ((belong[a.l] & 1) ? a.r < b.r : a.r > b.r):belong[a.l] < belong[b.l] ;
    }

    然后饱受运算优先级折磨的我卑微的修改加上了注释(实在是看不懂)

    while(l < ql) now -= (!--cnt[aa[l++]])/*del(l++)*/;//先cnt--,然后 !,然后++ 
    while(l > ql) now += (!cnt[aa[--l]]++)/*add(--l)*/;//先--l,然后!,然后++ 
    while(r < qr) now += (!cnt[aa[++r]]++)/*add(++r)*/;//先++r,然后!,然后++ 
    while(r > qr) now -= (!--cnt[aa[r--]])/*del(r--)*/;//先cnt--,然后!,然后--

    然后全部代码(并没有什么改动)

    #include<bits/stdc++.h>
    using namespace std;
    #define maxn 1010000
    #define maxb 1010
    #define isdigit(x) ((x) >= '0' && (x) <= '9')
    int aa[maxn], cnt[maxn], 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] & 1) ? a.r < b.r : a.r > b.r):belong[a.l] < belong[b.l] ;
    }
    /*void add(int pos) {
        if(!cnt[aa[pos]]) ++now;
        ++cnt[aa[pos]];
    }
    void del(int pos) {
        --cnt[aa[pos]];
        if(!cnt[aa[pos]]) --now;
    }*/
    int read() {
        int res = 0;
        char c = getchar();
        while(!isdigit(c)) c = getchar();
        while(isdigit(c)) res = (res << 1) + (res << 3) + c - 48, c = getchar();
        return res;
    }
    void printi(int x) {
        if(x / 10) printi(x / 10);
        putchar(x % 10 + '0');
    }
    int main() {
        scanf("%d", &n);
        size = sqrt(n);
        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) aa[i] = read(); 
        m = read();
        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, cmp);
        int l = 1, r = 0;
        for(int i = 1; i <= m; ++i) {
            int ql = q[i].l, qr = q[i].r;
            while(l < ql) now -= (!--cnt[aa[l++]])/*del(l++)*/;//先cnt--,然后 !,然后++ 
            while(l > ql) now += (!cnt[aa[--l]]++)/*add(--l)*/;//先--l,然后!,然后++ 
            while(r < qr) now += (!cnt[aa[++r]]++)/*add(++r)*/;//先++r,然后!,然后++ 
            while(r > qr) now -= (!--cnt[aa[r--]])/*del(r--)*/;//先cnt--,然后!,然后-- 
            ans[q[i].id] = now;
        }
        for(int i = 1; i <= m; ++i) printi(ans[i]), putchar('
    ');
        return 0;
    }

    接下来出场的是可修改莫队

    好题(毒瘤)

    这题似乎加强了数据搞的必须开O2才能过(题解里的神仙解法例外)

    普通莫队要离线下来做,遇到这种带修改的题目直接就萎了,但是全国广大的OIer们在莫队的基础上,发明了带修莫队这种玄学算法。

    对于某些允许离线的带修改区间查询来说,莫队还是能大展拳脚的。做法就是把莫队直接加上一维,变为带修莫队。

    具体来说就是给修改和求值打上时间戳,然后在普通莫队双指针的基础上增加一个指针time指向时间,在这一个维度上面跳来跳去,直到跳到左指针指向左端点,右指针指向右端点,time指针指向当前求值的时间戳为止。

    通俗地讲,就是再弄一指针,在修改操作上跳来跳去,如果当前修改多了就改回来,改少了就改过去,直到次数恰当为止。(来自大佬的言简意赅)Orz

    带修改莫队的排序:

    其实排序的主要方法还是跟普通莫队没两样,只不过是加了个关键字而已。

    排序函数:

    int cmp(ask a,ask b) {
        return (belong[a.l]==belong[b.l])?((belong[a.r]==belong[b.r])?a.time<b.time:belong[a.r]<belong[b.r]):belong[a.l]<belong[b.l];
    }

    主算法中的修改操作

    似乎与原来的修改区别不大,但值得一提的是,在修改的时候,由于我们的time指针可能“往回跳”,所以我们要记下来之前的值,因此我们可以直接swap这俩值,回来的时候就直接swap回来。(存下来也可以吧)

    分块大小和复杂度

    大佬证明:

    块大小为  可以达到最快的理论复杂度  ,证明如下

    设分块大小为a,莫队算法时间复杂度主要为t轴移动,同r块l,r移动,l块间的r移动三部分

    t轴移动的复杂度为  ,同r块l,r移动复杂度为  ,l块间的r移动复杂度为 

    三个函数max的最小值当a为  取得,为 

    证明了当块的大小设n4t−−−√3n4t3时理论复杂度达到最优,而块大小取n−−√n时会退化成O(n2)O(n2),不建议使用。

    然后就是毒瘤题的代码了并没有什么改动

    #include<bits/stdc++.h>
    using namespace std;
    #define maxn 1333340//毒瘤题数据加强了
    #define maxc 1001000
    int a[maxn],cnt[maxc],ans[maxn],belong[maxn];
    struct ask{
        int l, r, time, id;
    } q[maxn];
    int read(){
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9')ch=getchar();
        while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
        return x*f;
    }
    struct modify{
        int pos,color,last;
    } c[maxn];
    int cntq, cntc, n, m, size, bnum;
    int cmp(ask a,ask b) {
        return (belong[a.l]==belong[b.l])?((belong[a.r]==belong[b.r])?a.time<b.time:belong[a.r]<belong[b.r]):belong[a.l]<belong[b.l];
    }
    int main() {
        n=read(),m=read();
        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) 
            a[i] = read();
        for(int i = 1; i <= m; ++i) {
            char opt;
            opt=getchar();getchar(); 
            if(opt == 'Q') {
                q[++cntq].l=read();
                q[cntq].r=read();
                q[cntq].time=cntc;
                q[cntq].id =cntq;
            }
            else if(opt == 'R') {
                c[++cntc].pos=read();
                c[cntc].color=read();
            }
        }
        sort(q + 1, q + cntq + 1, cmp);
        int l = 1, r = 0, time = 0, now = 0;
        for(int i = 1; i <= cntq; ++i) {
            int ql = q[i].l, qr = q[i].r, qt = q[i].time;
            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(time < qt) {
                ++time;
                if(ql <= c[time].pos && c[time].pos <= qr) now -= (!--cnt[a[c[time].pos]] - !cnt[c[time].color]++);
                swap(a[c[time].pos], c[time].color);
            }
            while(time > qt) {
                if(ql <= c[time].pos && c[time].pos <= qr) now -= (!--cnt[a[c[time].pos]] - !cnt[c[time].color]++);
                swap(a[c[time].pos], c[time].color);
                --time;
            }
            ans[q[i].id] = now;
        }
        for(int i = 1; i <= cntq; ++i) 
            printf("%d
    ", ans[i]);
        return 0;
    }

    莫队算法的扩展——树上莫队

    路径上的统计

    题目

    分析:

     简单的DFS序并不能满足我们进行莫队的应用,因此我们需要用神奇的欧拉序图二来表示。

     结论:树的欧拉序上两个相同编号(设为xx)之间的所有编号都出现两次,且都位于xx子树上Orz

    大佬对于经过点是否加入答案的解释:树上路径的定义为:从xx到yy经过节点个数最少的路径。

    若一个点kk出现两次,说明我们可以先访问kk,进入kk的子树中,然后出来,再到yy,很显然不访问kk是更优的。因此出现两次的点不能统计入答案

    WAMonster大佬的具体做法非常详细:设每个点的编号aa首次出现的位置first[a]first[a],最后出现的位置为last[a]last[a],那么对于路径xyx→y,first[x]<=first[y]first[x]<=first[y]

    (不满足则swap,这个操作的意义在于,如果xx、yy在一条链上,则xx一定是yy的祖先或等于yy),如果lca(x,y)=xlca(x,y)=x,则直接把[first[x],first[y]][first[x],first[y]]的区间扯过来用

    ,反之使用[last[x],first[y]][last[x],first[y]]区间(为什么不用[first[x],first[y]][first[x],first[y]]?因为(first[x],last[x])(first[x],last[x])不会在路径上,根据性质,里面的编号都会出现两次,考虑了等于没考虑),

    但这个区间内不包含xx和yy的最近公共祖先,查询的时候加上即可。

    注意:序列长度要*2避免越界

    #include<bits/stdc++.h>
    using namespace std;
    #define maxn 200200
    #define isdigit(x) ((x) >= '0' && (x) <= '9')
    inline int read() {
        int res = 0;
        char c = getchar();
        while(!isdigit(c)) c = getchar();
        while(isdigit(c)) res = (res << 1) + (res << 3) + (c ^ 48), c = getchar();
        return res;
    }
    int aa[maxn], cnt[maxn], first[maxn], last[maxn], ans[maxn];
    int belong[maxn], inp[maxn], vis[maxn], ncnt, l = 1, r, now, size, bnum; //莫队相关
    int ord[maxn], val[maxn], head[maxn], depth[maxn], fa[maxn][30], ecnt;
    int n, m;
    struct edge {
        int to, next;
    } e[maxn];
    void adde(int u, int v) {
        e[++ecnt] = (edge){v, head[u]};
        head[u] = ecnt;
        e[++ecnt] = (edge){u, head[v]};
        head[v] = ecnt;
    }
    void dfs(int x) {
        ord[++ncnt] = x;
        first[x] = ncnt;
        for(int k = head[x]; k; k = e[k].next) {
            int to = e[k].to;
            if(to == fa[x][0]) continue;
            depth[to] = depth[x] + 1;
            fa[to][0] = x;
            for(int i = 1;(1 << i)<=depth[to]; ++i)
                fa[to][i] = fa[fa[to][i - 1]][i - 1];
            dfs(to);
        }
        ord[++ncnt] = x;
        last[x] = ncnt;
    }
    int getlca(int x, int y) {
        if (depth[x] < depth[y]) {
            swap(x, y);
        }
        int dd = depth[x] - depth[y];
        for (int i = 0; i < 20; i++) {
            if (dd >> i & 1) {
                x = fa[x][i];
            }
        }
        if (x == y) {
            return x;
        }
        for (int i = 20 - 1; i >= 0; i--) {
            if (fa[x][i] != fa[y][i]) {
                x = fa[x][i];
                y = fa[y][i];
            }
        }
        return fa[x][0];
    }
    struct query {
        int l, r, lca, id;
    } q[maxn];
    int cmp(query a, query b) {//对于左端点在同一奇数块的区间,右端点按升序排列,反之降序
        return (belong[a.l]==belong[b.l]) ? ((belong[a.l] & 1) ? a.r < b.r : a.r > b.r):belong[a.l] < belong[b.l] ;
    }
    void work(int pos) {
        vis[pos] ? now -= !--cnt[val[pos]] : now += !cnt[val[pos]]++;
        vis[pos] ^= 1;//不进位加法,重复两次的点不记入答案 
    }
    int main() {
        n = read(); m = read();
        for(int i = 1; i <= n; ++i) 
            val[i] = inp[i] = read();
        sort(inp + 1, inp + n + 1);
        int tot = unique(inp + 1, inp + n + 1) - inp - 1;
        for(int i = 1; i <= n; ++i)
            val[i] = lower_bound(inp + 1, inp + tot + 1, val[i]) - inp;
        for(int i = 1; i < n; ++i) adde(read(), read());
        depth[1] = 1;
        dfs(1);
        size = sqrt(ncnt), bnum = ceil((double) ncnt / size);
        for(int i = 1; i <= bnum; ++i)
            for(int j = size * (i - 1) + 1; j <= i * size; ++j) belong[j] = i;
        for(int i = 1; i <= m; ++i) {
            int L = read(), R = read(), lca = getlca(L, R);
            if(first[L] > first[R]) swap(L, R);
            if(L == lca) {
                q[i].l = first[L];
                q[i].r = first[R];
            }
            else {
                q[i].l = last[L];
                q[i].r = first[R];
                q[i].lca = lca;
            }
            q[i].id = i;
        }
        sort(q + 1, q + m + 1, cmp);
        for(int i = 1; i <= m; ++i) {
            int ql = q[i].l, qr = q[i].r, lca = q[i].lca;
            while(l < ql) work(ord[l++]);
            while(l > ql) work(ord[--l]);
            while(r < qr) work(ord[++r]);
            while(r > qr) work(ord[r--]);
            if(lca) work(lca);
            ans[q[i].id] = now;
            if(lca) work(lca);//消除掉本次操作的影响避免对下一次查询区间的影响 
        }
        for(int i = 1; i <= m; ++i) printf("%d
    ", ans[i]);
        return 0;
    }
    作者:wilxx
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    第三届 山东省ACM省赛
    省赛知识点待整理
    省赛知识点待整理
    最短路模板(Dijkstra & Dijkstra算法+堆优化 & bellman_ford & 单源最短路SPFA)
    最短路模板(Dijkstra & Dijkstra算法+堆优化 & bellman_ford & 单源最短路SPFA)
    最短路模板(Dijkstra & Dijkstra算法+堆优化 & bellman_ford & 单源最短路SPFA)
    hdoj 4883 TIANKENG’s restaurant【贪心区间覆盖】
    hdoj 1072 Nightmare
    hdoj 2141 Can you find it?【二分查找+暴力】
    poj 1064 Cable master【浮点型二分查找】
  • 原文地址:https://www.cnblogs.com/wilxx/p/11670839.html
Copyright © 2020-2023  润新知