带修改莫队

普通的莫队:对询问分块,计算相邻两个询问差距对答案的影响,统计答案。 O ( n n ) O(n\sqrt n) O(nn )

带修改莫队:记录当前进行了几次修改。如果当前修改比询问时的修改数少,就向前修改;如果当前修改比询问时的修改数多,就改回来。


时间复杂度

引用自洛谷题解

原版莫队是将区间 ( l , r ) (l,r) (l,r)视为点 ( l , r ) (l,r) (l,r),带修改的即加一维时间轴 ( l , r , t ) (l,r,t) (l,r,t)

对于tt轴的移动可以保存每次修改,如果修改在 ( l , r ) (l,r) (l,r)间则更新

分块方法可以参照原版莫队,先将 l l l分块,再讲 r r r分块,同一块的按 t t t排序

块大小为 n t 3 \sqrt[3]{nt} 3nt 可以达到最快的理论复杂度 O ( n 4 t 3 ) O(\sqrt[3]{n^4t}) O(3n4t ),证明如下

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

t t t轴移动的复杂度为 O ( n 2 t a 2 ) O(\frac {n^2t} {a^2}) O(a2n2t),同 r r r l , r l,r l,r移动复杂度为 O ( n a ) O(na) O(na) l l l块间的 r r r移动复杂度为 O ( n a ) O(\frac {n} {a}) O(an)

三个函数max的最小值当 a = n t 3 a=\sqrt[3] {nt} a=3nt 取得,为 O ( n 4 t 3 ) O(\sqrt[3]{n^4t}) O(3n4t )


bzoj2120 国家集训队 数颜色 / 维护队列

题意:两种操作:询问 [ l , r ] [l,r] [l,r]不同颜色的数量;修改 x x x位置的颜色。

题解:裸的带修改莫队

#include <bits/stdc++.h>
using namespace std;
inline int read() {
    int x = 0, f = 1; char c; c = getchar() ;
    while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar() ;
    return x * f ;
}
const int maxn = 5e4 + 10 ;
int n, m, qtot, ctot, ret ;
int a[maxn], blk[maxn], cnt[1000010], ans[maxn] ;
struct query {
    int l, r, pre, id ;
    query () {}
    query (int a, int b, int c, int d) {l = a, r = b, pre = c, id = d;}
    bool operator < (const query &a) const {
        if (l != a.l) return blk[l] < blk[a.l] ;
        if (r != a.r) r < a.r ;
        return pre < a.pre ;
    }
}q[maxn] ;
struct update {
    int pos, col ;
    update () {}
    update (int a, int b) {pos = a, col = b;}
}c[maxn] ;
void add (int val) {
    if (++ cnt[val] == 1) ret ++ ;
}
void del (int val) {
    if (-- cnt[val] == 0) ret -- ;
}
void work (int now, int i) {
    if (c[now].pos >= q[i].l && c[now].pos <= q[i].r) {
        if (-- cnt[a[c[now].pos]] == 0) ret -- ;
        if (++ cnt[c[now].col] == 1) ret ++ ;
    }
    swap (a[c[now].pos], c[now].col) ;
}
int main() {
    n = read(); m = read() ;
    for (int i = 1; i <= n; i ++) a[i] = read() ;
    int sz = pow (n, 0.6666666666666) ;
    for (int i = 1; i <= n; i ++) blk[i] = (i - 1) / sz + 1 ;
    while (m --) {
        char op[5] ;
        scanf("%s", op) ;
        int x = read(), y = read() ;
        if (op[0] == 'Q') {
            qtot ++ ;
            q[qtot] = query (x, y, ctot, qtot) ;
        } else {
            ctot ++ ;
            c[ctot] = update (x, y) ;
        }
    }
    sort (q + 1, q + qtot + 1) ;
    int l = 1, r = 0, now = 0 ;
    for (int i = 1; i <= qtot; i ++) {
        while (l < q[i].l) del (a[l ++]) ;
        while (l > q[i].l) add (a[-- l]) ;
        while (r < q[i].r) add (a[++ r]) ;
        while (r > q[i].r) del (a[r --]) ;
        while (now < q[i].pre) work (++ now, i) ;
        while (now > q[i].pre) work (now --, i) ;
        ans[q[i].id] = ret ;
    }
    for (int i = 1; i <= qtot; i ++) printf("%d\n", ans[i]) ;
    return 0 ;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值