分块九题

NO.1

题目:

给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间加法,单点查值。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>

using namespace std;
inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int maxn = 50005;

int n, L[maxn], R[maxn], pos[maxn], t;
long long a[maxn], add[maxn];

inline void update(int l, int r, int c) {
    int p = pos[l], q = pos[r];
    if (p == q) {
        for (int i = l; i <= r; ++ i) a[i] += c;
        return;
    }
    for (int i = l; i <= R[p]; ++ i) a[i] += c;
    for (int i = L[q]; i <= r; ++ i) a[i] += c;
    for (int i = p + 1; i <= q - 1; ++ i) add[i] += c;
}

int main() {
    n = read();
    t = sqrt(n);
    for (int i = 1; i <= n; ++ i) {
        a[i] = read();
    }
    for (int i = 1; i <= t; ++ i) {
        L[i] = (i - 1) * t + 1; R[i] = i * t;
    }
    if (R[t] < n) {
        ++ t;
        L[t] = R[t - 1] + 1;
        R[t] = n;
    }
    for (int i = 1; i <= t; ++ i) {
        for (int j = L[i]; j <= R[i]; ++ j) {
            pos[j] = i;
        }
    }
    int Q = n;
    while (Q --) {
        int opt = read(), l = read(), r = read(), c = read();
        if (opt == 0) {
            update(l, r, c);
        } else {
            printf("%lld\n", a[r] + add[pos[r]]);
        }
    }
    return 0;
}

NO.2

题目:

给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间加法,询问区间内小于某个值\(x\)的元素个数。

思路:

每次暴力修改块内元素时,暴力将块内元素重新排序。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>

using namespace std;
inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int maxn = 50005;

int n, t, L[maxn], R[maxn], pos[maxn];
long long a[maxn], add[maxn];

vector<long long>vec[maxn];

inline void bf_update(int l, int r, long long c) {
    int p = pos[l];
    for (int i = l; i <= r; ++ i) {
        a[i] += c;
    }
    vec[p].clear();
    for (int i = L[p]; i <= R[p]; ++ i) {
        vec[p].push_back(a[i]);
    }
    sort(vec[p].begin(), vec[p].end());
}

inline void update(int l, int r, long long c) {
    int p = pos[l], q = pos[r];
    if (p == q) {
        bf_update(l, r, c);
        return;
    }
    bf_update(l, R[p], c);
    bf_update(L[q], r, c);
    for (int i = p + 1; i <= q - 1; ++ i) {
        add[i] += c;
    }
}

inline int bf_count(int l, int r, long long c) {
    int res = 0, p = pos[l];
    for (int i = l; i <= r; ++ i) {
        res += ((a[i] + add[p]) < c);
    }
    return res;
}

inline int query(int l, int r, long long c) {
    int p = pos[l], q = pos[r];
    if (p == q) {
        return bf_count(l, r, c);
    }
    int res = 0;
    res += bf_count(l, R[p], c);
    res += bf_count(L[q], r, c);
    for (int i = p + 1; i <= q - 1; ++ i) {
        res += lower_bound(vec[i].begin(), vec[i].end(), c - add[i]) - vec[i].begin();
    }
    return res;
}

int main() {
    n = read();
    for (int i = 1; i <= n; ++ i) a[i] = read();
    t = sqrt(n);
    for (int i = 1; i <= t; ++ i) {
        L[i] = (i - 1) * t + 1;
        R[i] = i * t;
    }
    if (R[t] < n) {
        ++ t;
        L[t] = R[t - 1] + 1;
        R[t] = n;
    }
    for (int i = 1; i <= t; ++ i) {
        for (int j = L[i]; j <= R[i]; ++ j) {
            pos[j] = i;
            vec[i].push_back(a[j]);
        }
        sort(vec[i].begin(), vec[i].end());
    }
    int Q = n;
    while (Q --) {
        int opt = read(), l = read(), r = read();
        long long c = read();
        if (opt == 0) {
            update(l, r, c);
        }
        else {
            printf("%d\n", query(l, r, c * c));
        }
    }
    return 0;
}

NO.3

题目:

给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间加法,询问区间内小于某个值\(x\)的前驱(比其小的最大元素)。

思路:

和上题一样。不过这种做法不开O2就过不去,我觉得正解应该是分块套平衡树。大家不要被吓到,实际上平衡树用set代替就可以了。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>

using namespace std;

inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int maxn = 100005, maxm = 320;
const long long inf = 9223372036854775807;

int n, t, L[maxm], R[maxm], pos[maxn];
long long a[maxn], add[maxm];

vector<long long>S[maxm];

inline void bf_update(int l, int r, long long c) {
    int p = pos[l];
    for (int i = l; i <= r; ++ i) a[i] += c;
    S[p].clear();
    for (int i = L[p]; i <= R[p]; ++ i) S[p].push_back(a[i]);
    sort(S[p].begin(), S[p].end());
}

inline void update(int l, int r, long long c) {
    int p = pos[l], q = pos[r];
    if (p == q) {
        bf_update(l, r, c); return;
    }
    bf_update(l, R[p], c);
    bf_update(L[q], r, c);
    for (int i = p + 1; i <= q - 1; ++ i) add[i] += c;
}

inline long long bf_query(int l, int r, long long c) {
    long long mx = -inf;
    int p = pos[l];
    for (int i = l; i <= r; ++ i) {
        if (a[i] + add[p] < c) mx = max(mx, a[i] + add[p]);
    }
    return mx;
}

inline long long query(int l, int r, long long c) {
    int p = pos[l], q = pos[r];
    if (p == q) {
        long long tmp =  bf_query(l, r, c);
        if (tmp == -inf) return -1;
        else return tmp;
    }
    long long mx = -inf;
    mx = max(mx, bf_query(l, R[p], c));
    mx = max(mx, bf_query(L[q], r, c));
    for (int i = p + 1; i <= q - 1; ++ i) {
        int x = lower_bound(S[i].begin(), S[i].end(), c - add[i]) - S[i].begin();
        if (x == 0) continue;
        -- x;
        mx = max(mx, S[i][x] + add[i]);
    }
    if (mx == -inf) return -1;
    else return mx;
}

int main() {
    n = read(); t = sqrt(n);
    for (int i = 1; i <= n; ++ i) a[i] = read();
    for (int i = 1; i <= t; ++ i) {
        L[i] = (i - 1) * t + 1;
        R[i] = i * t;
    }
    if (R[t] < n) {
        ++ t;
        L[t] = R[t - 1] + 1;
        R[t] = n;
    }
    for (int i = 1; i <= t; ++ i) {
        for (int j = L[i]; j <= R[i]; ++ j) {
            pos[j] = i;
            S[i].push_back(a[j]);
        }
        sort(S[i].begin(), S[i].end());
    }
    while (n --) {
        int opt = read(), l = read(), r = read();
        long long c = read();
        if (opt == 0) {
            update(l, r, c);
        } else {
            printf("%lld\n", query(l, r, c));
        }
    }
    return 0;
}

NO.4

题目:

给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间加法,区间求和。

思路:

分块基操。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>

using namespace std;

inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int maxn = 50005, maxm = 230;

int n, t, L[maxm], R[maxm], pos[maxn];
long long a[maxn], add[maxm], sum[maxm];

inline void update(int l, int r, long long c) {
    int p = pos[l], q = pos[r];
    if (p == q) {
        for (int i = l; i <= r; ++ i) a[i] += c;
        sum[p] += c * (r - l + 1);
        return;
    }
    for (int i = l; i <= R[p]; ++ i) a[i] += c, sum[p] += c;
    for (int i = L[q]; i <= r; ++ i) a[i] += c, sum[q] += c;
    for (int i = p + 1; i <= q - 1; ++ i) add[i] += c, sum[i] += (R[i] - L[i] + 1) * c;
}

inline long long query(int l, int r) {
    int p = pos[l], q = pos[r];
    long long res = 0;
    if (p == q) {
        for (int i = l; i <= r; ++ i) res += a[i] + add[p];
        return res;
    }
    for (int i = l; i <= R[p]; ++ i) res += a[i] + add[p];
    for (int i = L[q]; i <= r; ++ i) res += a[i] + add[q];
    for (int i = p + 1; i <= q - 1; ++ i) res += sum[i];
    return res;
}

int main() {
    n = read(); t = sqrt(n);
    for (int i = 1; i <= n; ++ i) a[i] = read();
    for (int i = 1; i <= t; ++ i) {
        L[i] = (i - 1) * t + 1;
        R[i] = i * t;
    }
    if (R[t] < n) {
        ++ t;
        L[t] = R[t - 1] + 1;
        R[t] = n;
    }
    for (int i = 1; i <= t; ++ i) {
        for (int j = L[i]; j <= R[i]; ++ j) {
            pos[j] = i; sum[i] += a[j];
        }
    }
    while (n --) {
        int opt = read(), l = read(), r = read();
        long long c = read();
        if (opt == 0) {
            update(l, r, c);
        } else {
            long long res = query(l, r) % (c + 1);
            if (res < 0) res += (c + 1);
            printf("%lld\n", res);
        }
    }
    return 0;
}

NO.5

题目:

给出一个长为\(n\)的数列\(a_1\ldots a_n\),以及\(n\)个操作,操作涉及区间开方,区间求和。

思路:

从本题开始,问题难度稍微提升。

容易发现,一个在\(1\sim 2^{31}-1\)的数,最多开5次根就可以把它变成\(1\)

得到以下关于修改操作的算法:

边角暴力。对于一个整块,如果这个整块\(p\)满足\(sum(p)= L_p-R_p+1\)(也就是这个块中的元素全部是\(1\)),直接跳过这个块。否则暴力遍历块内的元素,进行修改。

看起来非常的暴力,但只不过将\(O(n\sqrt n)\)的复杂度多乘了一个常数而已。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>

using namespace std;

inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int maxn = 50005, maxm = 230;

long long a[maxn], sum[maxm];
int n, t, L[maxm], R[maxm], pos[maxn];

inline void update(int l, int r) {
    int p = pos[l], q = pos[r];
    if (p == q) {
        for (int i = l; i <= r; ++ i) {
            sum[p] -= a[i];
            a[i] = sqrt(a[i]);
            sum[p] += a[i];
        }
        return;
    }
    for (int i = l; i <= R[p]; ++ i) {
        sum[p] -= a[i];
        a[i] = sqrt(a[i]);
        sum[p] += a[i];
    }
    for (int i = L[q]; i <= r; ++ i) {
        sum[q] -= a[i];
        a[i] = sqrt(a[i]);
        sum[q] += a[i];
    }
    for (int i = p + 1; i <= q - 1; ++ i) {
        if (sum[i] == (R[i] - L[i] + 1)) continue;
        for (int j = L[i]; j <= R[i]; ++ j) {
            sum[i] -= a[j];
            a[j] = sqrt(a[j]);
            sum[i] += a[j];
        }
    }
}

inline long long query(int l, int r) {
    int p = pos[l], q = pos[r];
    long long res = 0;
    if (p == q) {
        for (int i = l; i <= r; ++ i) res += a[i];
        return res;
    }
    for (int i = l; i <= R[p]; ++ i) res += a[i];
    for (int i = L[q]; i <= r; ++ i) res += a[i];
    for (int i = p + 1; i <= q - 1; ++ i) res += sum[i];
    return res;
}

int main() {
    n = read();
    for (int i = 1; i <= n; ++ i) a[i] = read();
    t = sqrt(n);
    for (int i = 1; i <= t; ++ i) {
        L[i] = (i - 1) * t + 1;
        R[i] = i * t;
    }
    if (R[t] < n) {
        ++ t;
        L[t] = R[t - 1] + 1;
        R[t] = n;
    }
    for (int i = 1; i <= t; ++ i) {
        for (int j = L[i]; j <= R[i]; ++ j) {
            pos[j] = i;
            sum[i] += a[j];
        }
    }
    while (n --) {
        int opt = read(), l = read(), r = read(); read();
        if (opt == 0) {
            update(l, r);
        }
        else {
            printf("%lld\n", query(l, r));
        }
    }
    return 0;
}

NO.6

题目:

给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及单点插入,单点询问,数据随机生成。

思路:

考虑对每个块建一个链表。

由于本题数据随机生成,所以块的大小相对比较平衡。如果数据不是随机生成的,我们就需要在每次插入的时候判断一下,如果该块的大小有点太大了,就暴力重构每个块。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <list>
#include <cmath>

using namespace std;

inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int maxn = 200005, maxm = 505;

int n, a[maxn], t, tot;

list<int> arr[maxm];

inline void change(void) {// 重构
    tot = 0;
    for (int i = 1; i <= t; ++ i) {
        while (arr[i].size()) { a[++ tot] = arr[i].front(); arr[i].pop_front(); }
    }
    t = sqrt(tot);
    if (t * t < tot) ++ t;
    for (int i = 1; i <= t; ++ i) {
        for (int j = (i - 1) * t + 1; j <= min(i * t, tot); ++ j) {
            arr[i].push_back(a[j]);
        }
    }
}

inline void update(int p, int x) {
    int cnt = 0, i;
    for (i = 1; i <= t; ++ i) {
        cnt += arr[i].size();
        if (cnt >= p) break;
    }
    cnt -= arr[i].size();
    p -= (cnt + 1);
    list<int>:: iterator it = arr[i].begin();
    while (p --) ++ it;
    arr[i].insert(it, x);
    if ((int)arr[i].size() > t * 20) change();// 判断是否失去平衡
}

inline int query(int p) {
    int cnt = 0, i;
    for (i = 1; i <= t; ++ i) {
        cnt += arr[i].size();
        if (cnt >= p) break;
    }
    cnt -= arr[i].size();
    p -= (cnt + 1);
    list<int>:: iterator it = arr[i].begin();
    while (p --) ++ it;
    return (*it);
}

int main() {
    n = read();
    for (int i = 1; i <= n; ++ i) a[i] = read();
    t = sqrt(n);
    if (t * t < n) ++ t;
    for (int i = 1; i <= t; ++ i) {
        for (int j = (i - 1) * t + 1; j <= min(i * t, n); ++ j) {
            arr[i].push_back(a[j]);
        }
    }
    while (n --) {
        int opt = read(), l = read(), r = read(); read();
        if (opt == 0) {
            update(l, r);
        }
        else {
            printf("%d\n", query(r));
        }
    }
    return 0;
}

NO.7

题目:

一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间乘法,区间加法,单点询问。

思路:

注意到这道题和前面几道题的不同之处。前面几道题在边角暴力的时候,“修改操作”和“标记”是完全独立的。也就是说,我暴力修改一个块中某些元素的值,不会影响标记。但是这道题不一样。对于一个块内的某些元素被修改时,以前的标记就会变得不正确。

举个例子,比如说有一个元素的值为\(a_i=3\),它属于块\(p\)。而且\(add_p=2\)\(mult_p=3\)。那么它真实的值就应该是\(3\times3+2=11\)

现在如果我单独对这一个元素进行修改,比如说把它加\(1\)。那么修改后正确的值应该是\(11+1=12\)。但是如果我不管标记,直接将\(a_i\)先变成\(4\)。那么修改过后的值就变成了\(4\times 3+2\neq 12\)。这显然是错误的。

所以我们需要在边角暴力的时候,先将块重构一遍,再进行边角修改。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>

using namespace std;
inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int maxn = 100005, mod = 10007, maxm = 350;

int n, t;
int a[maxn], L[maxn], R[maxn], pos[maxn], add[maxn], mult[maxn];

inline void rebuild(int p) {
    for (int i = L[p]; i <= R[p]; ++ i) {
        (a[i] *= mult[p]) %= mod;
        (a[i] += add[p]) %= mod;
    }
    mult[p] = 1; add[p] = 0;
}

inline void update1(int l, int r, int c) {
    int p = pos[l], q = pos[r];
    if (p == q) {
        rebuild(p);
        for (int i = l; i <= r; ++ i) (a[i] += c) %= mod;
        return;
    }
    rebuild(p);
    for (int i = l; i <= R[p]; ++ i) (a[i] += c) %= mod;
    rebuild(q);
    for (int i = L[q]; i <= r; ++ i) (a[i] += c) %= mod;
    for (int i = p + 1; i <= q - 1; ++ i) (add[i] += c) %= mod;
}

inline void update2(int l, int r, int c) {
    int p = pos[l], q = pos[r];
    if (p == q) {
        rebuild(p);
        for (int i = l; i <= r; ++ i) (a[i] *= c) %= mod;
        return;
    }
    rebuild(p);
    for (int i = l; i <= R[p]; ++ i) (a[i] *= c) %= mod;
    rebuild(q);
    for (int i = L[q]; i <= r; ++ i) (a[i] *= c) %= mod;
    for (int i = p + 1; i <= q - 1; ++ i) {
        (add[i] *= c) %= mod;
        (mult[i] *= c) %= mod;
    }
}

inline int query(int r) {
    int q = pos[r];
    return (a[r] * mult[q] % mod + add[q]) % mod;
}

int main() {
    n = read();
    for (int i = 1; i <= n; ++ i) {
        a[i] = read() % mod;
    }
    t = sqrt(n);
    for (int i = 1; i <= t; ++ i) {
        L[i] = (i - 1) * t + 1;
        R[i] = i * t;
    }
    if (R[t] < n) {
        ++ t;
        L[t] = R[t - 1] + 1;
        R[t] = n;
    }
    for (int i = 1; i <= t; ++ i) {
        mult[i] = 1;
        for (int j = L[i]; j <= R[i]; ++ j) {
            pos[j] = i;
        }
    }
    for (int i = 1; i <= n; ++ i) {
        int opt = read(), l = read(), r = read(), c = read() % mod;
        if (opt == 0) {
            update1(l, r, c);
        }
        else if (opt == 1) {
            update2(l, r, c);
        }
        else if (opt == 2) {
            printf("%d\n", query(r));
        }
    }
    return 0;
}

NO.8

题目:

给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间询问等于一个数\(c\)的元素个数,并将这个区间的所有元素改为\(c\)

思路:

个人认为这道题如果用线段树更为优雅,但是这是分块专题,就不得不想一想分块怎么做。

首先能想到一个非常暴力的想法。对于任意一个块\(p\),标记一下它有没有被覆盖过。覆盖指的是块\(p\)中的所有元素是否被赋成了同一个值。

对于一个操作,首先边角暴力。边角的块将标记下放,然后再进行暴力修改。然后对于中间的整块,如果这个块被覆盖过,那就直接统计答案,否则,暴力统计答案。

看起来复杂度非常的不对。理由是如果一次操作中的所有块全部没有被覆盖过,那么这一次操作的复杂度就是\(O(n)\)的。

但是,我们来想一个问题。如果初始时每个元素都相同,那么要想达到每个块都没有被完整地覆盖过,首先需要\(O(\sqrt n)\)次操作。所以感性的理解一下,每次操作的均摊复杂度仍是\(O(\sqrt n)\)的。

听起来比较玄学。而且这种方法之所以能用只是由于这道题目的特殊性(一次询问必定紧跟一次修改)。所以,博主的建议是,如果考试的时候遇到类似的问题,乖乖打线段树吧。

代码:

// 开O2才能过
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>

inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int maxn = 1e5 + 5, maxm = 320;

int n, a[maxn], t;
int L[maxm], R[maxm], pos[maxn], g[maxm];
bool tag[maxm];

inline int bf_update(int l, int r, int c) {
    int p = pos[l];
    if (tag[p]) {
        for (int i = L[p]; i <= R[p]; ++ i) a[i] = g[p];
        for (int i = l; i <= r; ++ i) a[i] = c;
        tag[p] = false;
        if (g[p] == c) return r - l + 1;
        else return 0;
    }
    int res = 0;
    for (int i = l; i <= r; ++ i) {
        res += (a[i] == c);
        a[i] = c;
    }
    return res;
}

inline int update(int l, int r, int c) {
    int p = pos[l], q = pos[r];
    if (p == q) {
        return bf_update(l, r, c);
    }
    int res = 0;
    res += bf_update(l, R[p], c);
    res += bf_update(L[q], r, c);
    for (int i = p + 1; i <= q - 1; ++ i) {
        if (tag[i]) {
            if (g[i] == c) res += R[i] - L[i] + 1;
        }
        else {
            for (int j = L[i]; j <= R[i]; ++ j) {
                res += (a[j] == c);
            }
        }
        tag[i] = true;
        g[i] = c;
    }
    return res;
}

int main() {
    n = read(); t = sqrt(n);
    for (int i = 1; i <= n; ++ i) a[i] = read();
    for (int i = 1; i <= t; ++ i) {
        L[i] = (i - 1) * t + 1;
        R[i] = i * t;
    }
    if (R[t] < n) {
        ++ t;
        L[t] = R[t - 1] + 1;
        R[t] = n;
    }
    for (int i = 1; i <= t; ++ i) {
        for (int j = L[i]; j <= R[i]; ++ j) {
            pos[j] = i;
        }
    }
    while (n --) {
        int l = read(), r = read(), c = read();
        printf("%d\n", update(l, r, c));
    }
    return 0;
}

NO.9

题目:

给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及询问区间的最小众数。

思路:

此题离线,当然可以用莫队直接秒掉。

但是分块的做法可以在线回答这些询问。

考虑将整个序列分成\(T\)块,每块的大小为\(n/T\)。首先预处理出pair数组\(mode\)\(mode[i,j].second\)表示从\(L_i\)\(R_j\)的最小众数,\(mode[i,j].first\)表示从\(L_i\)\(R_j\)最小众数出现的次数。预处理的复杂度\(O(nT)\)

再预处理出vector数组\(position\)\(position[x]\)表示数值为\(x\)出现的位置集合。

对于一个询问\([l,r]\)。将询问分成三个部分:\([l,L),[L,R],(R,r]\)

对于部分\([L,R]\),直接调用\(mode\)数组进行查询。

对于部分\([l,L)\),我们扫描\([l,L)\)中的每一个元素\(x\),在\(position\)数组中二分找出\(x\)\([l,r]\)中出现了多少次,更新答案。

对于部分\((R,r]\),与上一部分类似。

每次每次询问的复杂度\(O(\dfrac{n}{T}\log_2n)\)

所以总的复杂度\(O(nT+\dfrac{n^2\log_2n}{T})\)。运用均值不等式,得出\(T\)的最优取值\(T=\sqrt{n\log_2n}\)。最终的复杂度\(O(n\sqrt{n\log_2n})\)

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>

using namespace std;

inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int maxn = 100005, maxm = 1300;

int n, a[maxn], t, L[maxm], R[maxm], pos[maxn], b[maxn], tot, len;
int cnt[maxn];

vector<int>position[maxn];
pair<int, int>mode[maxm][maxm];

inline void lsh(void) {// 别忘了离散化
    for (int i = 1; i <= n; ++ i) b[i] = a[i];
    sort(b + 1, b + n + 1);
    tot = unique(b + 1, b + n + 1) - b - 1;
    for (int i = 1; i <= n; ++ i) {
        a[i] = lower_bound(b + 1, b + tot + 1, a[i]) - b;
    }
    double tmp = n;
    t = max(sqrt(tmp * log2(tmp)), 1.0);
    len = n / t;
}

inline int bf_query(int l, int r, int L, int R, int &num) {
    int mx = 0, res = -1;
    for (int i = l; i <= r; ++ i) {
        int x = a[i];
        int tmp1 = lower_bound(position[x].begin(), position[x].end(), L) - position[x].begin();
        int tmp2 = upper_bound(position[x].begin(), position[x].end(), R) - position[x].begin();
        if (tmp2 - tmp1 == res) {
            mx = min(mx, x);
        } else if (tmp2 - tmp1 > res) {
            res = tmp2 - tmp1;
            mx = x;
        }
    }
    num = res;
    return mx;
}

inline int query(int l, int r) {
    int p = pos[l], q = pos[r], num = 0;
    if (p == q) {
        return bf_query(l, r, l, r, num);
    }
    int mx = (p == q - 1 ? 0 : mode[p + 1][q - 1].second), res = (p == q - 1 ? 0 : mode[p + 1][q - 1].first);

    int tmp = bf_query(l, R[p], l, r, num);
    if (num == res) mx = min(mx, tmp);
    else if (num > res) { res = num; mx = tmp; }

    tmp = bf_query(L[q], r, l, r, num);

    if (num == res) mx = min(mx, tmp);
    else if (num > res) { res = num; mx = tmp; }
    return mx;
}

int main() {
    n = read();
    for (int i = 1; i <= n; ++ i) a[i] = read();
    lsh();
    for (int i = 1; i <= t; ++ i) {
        L[i] = (i - 1) * len + 1;
        R[i] = i * len;
    }
    if (R[t] < n) {
        ++ t;
        L[t] = R[t - 1] + 1;
        R[t] = n;
    }
    for (int i = 1; i <= t; ++ i) {
        for (int j = L[i]; j <= R[i]; ++ j) {
            pos[j] = i;
        }
    }
    for (int i = 1; i <= t; ++ i) {
        int p = L[i];
        memset(cnt, 0, sizeof cnt);
        int now = p - 1, mx = 0;
        for (int j = i; j <= t; ++ j) {
            while (now < R[j]) {
                ++ now;
                ++ cnt[a[now]];
                if (cnt[a[now]] == cnt[mx]) {
                    mx = min(mx, a[now]);
                } else if (cnt[a[now]] > cnt[mx]) mx = a[now];
            }
            mode[i][j].first = cnt[mx];
            mode[i][j].second = mx;
        }
    }
    for (int i = 1; i <= tot; ++ i) position[i].push_back(-1);
    for (int i = 1; i <= n; ++ i) {
        position[a[i]].push_back(i);
    }
    for (int i = 1; i <= tot; ++ i) position[i].push_back(n + 1);

    for (int i = 1; i <= n; ++ i) {
        int l = read(), r = read();
        printf("%d\n", b[query(l, r)]);
    }
    return 0;
}

至此,分块九题的讲解全部结束。完结撒花。

posted @ 2021-03-29 17:42  蓝田日暖玉生烟  阅读(67)  评论(0编辑  收藏  举报