分块思想基础莫队

分块

将数组分成sqrt(n)块,每次进行区间操作或者查询的时候,对于完整的块可以通过预处理的信息o1得到,
不完整的块直接暴力跑,所以最坏复杂度是sqrt(n)。
分块模板

const int N = 100010, B = sqrt(N);
int block;
int st[B], ed[B], bel[N];
int sum[B], tag[B];
int a[N], sz[B];
void init(int n) {
    block = sqrt(n + 0.5);
    for (int i = 1; i <= block; i++) {
        st[i] = n / block * (i - 1) + 1; // st[i]表示i号块的第一个元素的下标
        ed[i] = n / block * i;
    }
    ed[block] = n;
    for (int i = 1; i <= block; i++) {
        for (int j = st[i]; j <= ed[i]; j++) {
            bel[j] = i;//编号
            //sum[i] += a[j];
        }
        sz[i] = ed[i] - st[i] + 1; //大小
    }
}

查询和修改操作

int query(int l, int r) {
    int ans = 0;
    if (bel[l] == bel[r]) {
        for (int i = l; i <= r; i++) {
            ans += a[i] + tag[bel[i]];
        }
        return ans;
    } else {
        for (int i = l; i <= ed[bel[l]]; i++) {
            ans += a[i] + tag[bel[i]];
        }
        for (int i = st[bel[r]]; i <= r; i++) {
            ans += a[i] + tag[bel[i]];
        }
        int L = bel[l] + 1, R = bel[r] - 1;
        for (int i = L; i <= R; i++) {
            ans += sum[i] + sz[i] * tag[i];
        }
        return ans;
    }

}
void modify(int l, int r, int x) {
    if (bel[l] == bel[r]) {
        for (int i = l; i <= r; i++) {//在一个快
            a[i] += x;
            sum[bel[i]] += x;
        }
    } else {
        for (int i = l; i <= ed[bel[l]]; i++) { //左边
            a[i] += x;
            sum[bel[i]] += x;
        }
        for (int i = st[bel[r]]; i <= r; i++) { //右边
            a[i] += x;
            sum[bel[i]] += x;
        }
        int L = bel[l] + 1, R = bel[r] - 1;
        for (int i = L; i <= R; i++) {
            // sum[i] += sz[i] * x;
            tag[i] += x;
        }
    }
}

P3372 【模板】线段树 1
直接套用模板即可

const int N = 100010, B = sqrt(N);
int block;
int st[B], ed[B], bel[N];
int sum[B], tag[B];
int a[N], sz[B];
void init(int n) {
    block = sqrt(n + 0.5);
    for (int i = 1; i <= block; i++) {
        st[i] = n / block * (i - 1) + 1; // st[i]表示i号块的第一个元素的下标
        ed[i] = n / block * i;
    }
    ed[block] = n;
    for (int i = 1; i <= block; i++) {
        for (int j = st[i]; j <= ed[i]; j++) {
            bel[j] = i;//编号
            sum[i] += a[j];
        }
        sz[i] = ed[i] - st[i] + 1; //大小
    }
}
int query(int l, int r) {
    int ans = 0;
    if (bel[l] == bel[r]) {
        for (int i = l; i <= r; i++) {
            ans += a[i] + tag[bel[i]];
        }
        return ans;
    } else {
        for (int i = l; i <= ed[bel[l]]; i++) {
            ans += a[i] + tag[bel[i]];
        }
        for (int i = st[bel[r]]; i <= r; i++) {
            ans += a[i] + tag[bel[i]];
        }
        int L = bel[l] + 1, R = bel[r] - 1;
        for (int i = L; i <= R; i++) {
            ans += sum[i] + sz[i] * tag[i];
        }
        return ans;
    }

}
void modify(int l, int r, int x) {
    if (bel[l] == bel[r]) {
        for (int i = l; i <= r; i++) {//在一个快
            a[i] += x;
            sum[bel[i]] += x;
        }
    } else {
        for (int i = l; i <= ed[bel[l]]; i++) { //左边
            a[i] += x;
            sum[bel[i]] += x;
        }
        for (int i = st[bel[r]]; i <= r; i++) { //右边
            a[i] += x;
            sum[bel[i]] += x;
        }
        int L = bel[l] + 1, R = bel[r] - 1;
        for (int i = L; i <= R; i++) {
            // sum[i] += sz[i] * x;
            tag[i] += x;
        }
    }
}
void solve(int Case) {

    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    init(n);
    for (int i = 1; i <= m; i++) {
        int opt, l, r, c;
        cin >> opt >> l >> r ;
        if (opt == 1) {
            cin >> c;
            modify(l, r, c);
        } else {
            cout << query(l, r) << nline;
        }

    }
}

P2801 教主的魔法
区间加操作分块可以轻松完成,对于查询大于等于w的数字,不完整的块直接暴力跑,完整的块可以排序二分求;
首先预处理每个块并且排好序,对于每次修改不完整的块,单独进行一次排序,这样就保证块内始终是有序的;

const int N = 2000100, B = sqrt(N);
int block;
int st[B], ed[B], bel[N];
int sum[B], tag[B];
int a[N], sz[B];
int c[N];
void Sort(int k) {
    int l = st[k], r = ed[k];
    for (int i = l; i <= r; i++) c[i] = a[i];
    sort(c + l, c + r + 1);
}
void init(int n) {
    block = sqrt(n + 0.5);
    for (int i = 1; i <= block; i++) {
        st[i] = n / block * (i - 1) + 1; // st[i]表示i号块的第一个元素的下标
        ed[i] = n / block * i;
    }
    ed[block] = n;
    for (int i = 1; i <= block; i++) {
        for (int j = st[i]; j <= ed[i]; j++) {
            bel[j] = i;//编号
            sum[i] += a[j];
        }
        sz[i] = ed[i] - st[i] + 1; //大小
    }
    for (int i = 1; i <= n; i++) c[i] = a[i];
    for (int i = 1; i <= block; i++) {
        Sort(i);
    }

}
int query(int l, int r, int k) {
    int ans = 0;
    if (bel[l] == bel[r]) {
        for (int i = l; i <= r; i++) {
            ans += (a[i] + tag[bel[i]] >= k);
        }
        return ans;
    } else {
        for (int i = l; i <= ed[bel[l]]; i++) {
            ans += (a[i] + tag[bel[i]] >= k);
        }
        for (int i = st[bel[r]]; i <= r; i++) {
            ans += (a[i] + tag[bel[i]]) >= k;
        }
        int L = bel[l] + 1, R = bel[r] - 1;
        for (int i = L; i <= R; i++) {
            //
            ans += ed[i] - (lower_bound(c + st[i], c + ed[i] + 1, k - tag[i]) - c) + 1;
        }
        return ans;
    }

}
void modify(int l, int r, int x) {
    if (bel[l] == bel[r]) {
        for (int i = l; i <= r; i++) {//在一个快
            a[i] += x;
            sum[bel[i]] += x;
        }
        Sort(bel[l]);
    } else {
        for (int i = l; i <= ed[bel[l]]; i++) { //左边
            a[i] += x;
            sum[bel[i]] += x;
        }
        Sort(bel[l]);
        for (int i = st[bel[r]]; i <= r; i++) { //右边
            a[i] += x;
            sum[bel[i]] += x;
        }
        Sort(bel[r]);
        int L = bel[l] + 1, R = bel[r] - 1;
        for (int i = L; i <= R; i++) {
            // sum[i] += sz[i] * x;
            tag[i] += x;
        }
    }
}
void solve(int Case) {

    int n, q;
    cin >> n >> q;
    for (int i = 1; i <= n; i++) cin >> a[i];
    init(n);
    for (int i = 1; i <= q; i++) {
        int l, r, w;
        char op[2];
        cin >> op;
        cin >> l >> r >> w;
        if (op[0] == 'M') {
            modify(l, r, w);
        } else {
            cout << query(l, r, w) << nline;
        }
    }
}

莫队

莫队算法具体操作是把查询区间按照左端点分块,块内右端点有序;
这样每次l能够移动的范围是sqrt(n),因为r是单调的,每个块最多跑n次,一共sqrt(n)个块,所以整体复杂度就是msqrt(n),m是查询次数
P1494 [国家集训队] 小 Z 的袜子

贡献区间内所有袜子种类x,sum(C(cnt[x],2))/C(len,2),len为长度,单独考虑每增加一个x类袜子,就多出cnt[x]种可能,每减少一个x,就减少cnt[x]种可能。

const int N = 500100;
int a[N];
int block;
struct T {
    int l, r, id;
    bool operator<(const T &t)const {
        if (l / block != t.l / block) return l / block < t.l / block;
        return r < t.r;
    }
} q[N];
using PII = pair<int, int>;
PII ans[N];
int vis[N];
int cnt = 0;
void del(int x) {
    vis[x]--;
    cnt -= vis[x];
}
void add(int x) {
    ++vis[x];
    cnt += vis[x] - 1;
}
// int ans[N];
void solve(int Case) {
    int n, m;
    cin >> n >> m;
    block = sqrt(n + 0.5);
    for (int i = 1; i <= n; i++) cin >> a[i];
    // cin >> m;
    for (int i = 1; i <= m; i++) {
        auto &[l, r, id] = q[i];
        cin >> l >> r;
        id = i;
    }
    sort(q + 1, q + 1 + m);
    int l = 1, r = 1;
    cnt = 0;
    vis[a[1]] = 1;
    for (int i = 1; i <= m; i++) {
        auto &[x, y, id] = q[i];
        while (l > x) add(a[--l]);
        while (r < y) add(a[++r]);
       
        while (l < x) del(a[l++]);
        
        while (r > y) del(a[r--]);
        auto&[f, s] = ans[id];

        f = cnt, s = (y - x + 1) * (y - x) / 2;
        int g = __gcd(f, s);
        if (x == y) {
            f = 0, s = 1;
            continue;
        }
        f /= g;
        s /= g;
    }
    for (int i = 1; i <= m; i++) {
        auto &[f, s] = ans[i];
        cout << f << '/' << s << nline;
    }
}

E. XOR and Favorite Number
考虑前缀异或和,s[i]^s[j-1]=k,则说明j~i的异或和等于k,可以统计前缀异或和的个数,然后类似上题

const int N = 2000100;
int a[N], s[N];
int vis[N];
int block;
int ans[N];
struct T {
    int l, r, id;
    bool operator<(const T &t) const {
        if (l / block != t.l / block) return l / block < t.l / block;
        return r < t.r;
    }
} q[N];
int cnt = 0;
int n, m, k;
void del(int x) {
    vis[x]--;
    cnt -= vis[x ^ k];
}
void add(int x) {
    cnt += vis[x ^ k];
    vis[x]++;
}
void solve(int Case) {
    cin >> n >> m >> k;
    block = sqrt(n + 0.5);
    for (int i = 1; i <= n; i++) cin >> a[i], s[i] = s[i - 1] ^ a[i];
    for (int i = 1; i <= m; i++) {
        auto &[l, r, id] = q[i];
        cin >> l >> r;
        id = i;
    }
    sort(q + 1, q + 1 + m);
    int l = 1, r = 0;
    vis[0]++;
    for (int i = 1; i <= m; i++) {
        auto [x, y, id] = q[i];
        while (l > x) l--,add(s[l-1]);
        while (r < y) add(s[++r]);
        while (l < x) del(s[l-1]),l++;
        while (r > y) del(s[r--]);
 
        ans[id] = cnt;
    }
    for (int i = 1; i <= m; i++) cout << ans[i] << nline;
}

P4137 Rmq Problem / mex
判断每个数字是否出现过,这个过程可以通过分块来判断,如果一个块内数字全部出现则直接跳过,否则mex就在这个块内
配合莫队,整体复杂度m*sqrt(n)

const int N = 200010, B = sqrt(N);
int a[N];
int block;
int ans[N];
int f[B];
int sz[B], st[B], ed[B], bel[N];
int cnt[N];
void init(int n) {
    block = sqrt(n + 0.5);
    for (int i = 1; i <= block; i++) {
        st[i] = n / block * (i - 1) + 1; // st[i]表示i号块的第一个元素的下标
        ed[i] = n / block * i;
    }
    ed[block] = n;
    for (int i = 1; i <= block; i++) {
        for (int j = st[i]; j <= ed[i]; j++) {
            bel[j] = i;//编号
        }
        sz[i] = ed[i] - st[i] + 1; //大小
    }
}

struct T {
    int l, r, id;
    bool operator<(const T &t) const {
        if (l / block != t.l / block) return l / block < t.l / block;
        return r < t.r;
    }
} q[N];
int n, m, k;

void del(int x) {
    if (x > n + 1) return;
    cnt[x]--;
    if (!cnt[x]) {
        f[bel[x]]--;
    }
}
void add(int x) {
    if (x > n + 1) return;
    cnt[x]++;
    if (cnt[x] == 1) {
        f[bel[x]]++;
    }
}
int query() {
    for (int i = 1; i <= block; i++) {
        if (f[i] == sz[i]) continue;
        else {
            for (int j = st[i]; j <= ed[i]; j++) {
                if (cnt[j] == 0) return j;
            }
        }
    }
    return n + 2;
}
void solve(int Case) {
    cin >> n >> m;
    block = sqrt(n + 0.5);
    for (int i = 1; i <= n; i++) cin >> a[i], a[i]++;
    init(n+1);
    for (yint i = 1; i <= m; i++) {
        auto &[l, r, id] = q[i];
        cin >> l >> r;
        id = i;
    }
    sort(q + 1, q + 1 + m);
    int l = 1, r = 0;
    for (int i = 1; i <= m; i++) {
        auto [x, y, id] = q[i];
        while (l > x) add(a[--l]);
        while (r < y) add(a[++r]);
       
        while (l < x) del(a[l++]);
        
        while (r > y) del(a[r--]);
        ans[id] = query() - 1;
    }
    for (int i = 1; i <= m; i++) cout << ans[i] << nline;
}

带修莫队

多增加一维时间
复杂度证明参考
https://oi-wiki.org/misc/modifiable-mo-algo/
P1903 [国家集训队] 数颜色 / 维护队列

分别处理两个数组一个查询一个修改
块取n^(2/3)
排序1:左端点块2:右端点块3:修改时间
首先按照常规莫队调整区间
如果当前修改时间小于查询时修改时间,往后面修改
反之亦然,(注意在区间范围内的要对答案进行修改)

const int N = 1000100;
int vis[N], a[N], ans[N];
struct ch {
    int p, c;

} c[N];
int block;
struct T {
    int l, r, t, id;
    bool operator<(const T &p) const {
        if (l / block == p.l / block) {
            if (r / block == p.r / block) {
                return t < p.t;
            }
            return r / block < p.r / block;
        }
        return l / block < p.l / block;
    }
} q[N];
int res = 0;
void add(int x) {
    if (++vis[x] == 1) res++;
}
void del(int x) {
    if (--vis[x] == 0) res--;
}
void modify(int qt, int ct) {//当前查询区间,修改时间
    if (c[ct].p >= q[qt].l and c[ct].p <= q[qt].r) {
        del(a[c[ct].p]);
        add(c[ct].c);
    }
    swap(a[c[ct].p], c[ct].c);//交换,方便回退
}
int l = 1, r = 0;
void solve(int Case) {
    int n, m;
    cin >> n >> m;
    block = pow(n, 0.66666);
    for (int i = 1; i <= n; i++) cin >> a[i];
    int qcnt = 0, ccnt = 0;
    char  op[2];
    for (int i = 1; i <= m; i++) {
        cin >> op;
        int l, r;
        if (op[0] == 'Q') {
            cin >> l >> r;
            q[++qcnt] = {l, r, ccnt, qcnt};
        } else {
            int pos, col;
            cin >> pos >> col;
            c[++ccnt] = {pos, col};
        }
    }
    sort(q + 1, q + 1 + qcnt);
    int now = 0;
    for (int i = 1; i <= qcnt; i++) {
        auto [x, y, t, id] = q[i];
        while (l > x) add(a[--l]);
        while (r < y) add(a[++r]);
        while (l < x) del(a[l++]);
        while (r > y) del(a[r--]);
        while (now < t) modify(i, ++now);
        while (now > t) modify(i, now--);
        ans[id] = res;
    }
    for (int i = 1; i <= qcnt; i++) {
        cout << ans[i] << nline;
    }
}

本文参考oiwiki
https://oi-wiki.org/ds/decompose/
https://oi-wiki.org/ds/block-array/

posted @ 2023-04-30 17:41  指引盗寇入太行  阅读(21)  评论(0编辑  收藏  举报