Luogu P3793 由乃救爷爷

分块乱搞,设块大小为 \(len\)。

我们暴力预处理每个块前缀、后缀的最大值,用 ST表 求出任意连续块之间的最大值。

预处理复杂度为 \(O(\dfrac{n}{len}\log\dfrac{n}{len}+n+n) = O(n)\)。

查询的时候找中间夹着的块的最大值、左侧后缀和右侧前缀。

查询显然是 \(O(1)\) 的。

但是这个做法有问题。当左右端点在同一块内时就假了。

这一题数据随机,所以我们可以暴力,并且复杂度是对的。

两个端点在同一块内的概率是 \(\dfrac{len}{n}\),每次查询的代价是 \(len\),所以期望为 \(O(\dfrac{len^2}{n})\)。

令 \(len = \sqrt n\) 每次暴力查询期望复杂度为 \(O(1)\)。

这样我们就得到了一个 \(O(n)\) 预处理, \(O(1)\) 查询的做法。

#include <algorithm>
#include <cmath>
#include <cstdio>
using namespace std;
const int N = 2e7 + 5;
int n, m, s, block;
int st[N/100][25], a[N];
int suf[N], pre[N];
unsigned long long ans;
namespace GenHelper {
unsigned z1, z2, z3, z4, b;
unsigned rand_() {
    b = ((z1 << 6) ^ z1) >> 13;
    z1 = ((z1 & 4294967294U) << 18) ^ b;
    b = ((z2 << 2) ^ z2) >> 27;
    z2 = ((z2 & 4294967288U) << 2) ^ b;
    b = ((z3 << 13) ^ z3) >> 21;
    z3 = ((z3 & 4294967280U) << 7) ^ b;
    b = ((z4 << 3) ^ z4) >> 12;
    z4 = ((z4 & 4294967168U) << 13) ^ b;
    return (z1 ^ z2 ^ z3 ^ z4);
}
} // namespace GenHelper
void srand(unsigned x) {
    using namespace GenHelper;
    z1 = x;
    z2 = (~x) ^ 0x233333333U;
    z3 = x ^ 0x1234598766U;
    z4 = (~x) + 51;
}
int read() {
    using namespace GenHelper;
    int a = rand_() & 32767;
    int b = rand_() & 32767;
    return a * 32768 + b;
}
int getpos(int x) { return (x + block - 1) / block; }
int bf(int l, int r) {
    int res = a[l];
    for (int i = l; i <= r; i++)
        res = max(res, a[i]);
    return res;
}
void init() {
    int p = getpos(n);
    for (int len = 1; len <= 20; len++)
        for (int i = 1; i <= p - (1 << len) + 1; i++)
            st[i][len] = max(st[i][len - 1], st[i + (1 << (len - 1))][len - 1]);
    for (int i = 1; i <= n; i++)
        if (i % block != 1)
            pre[i] = max(pre[i - 1], a[i]);
        else
            pre[i] = a[i];
    for (int i = n; i >= 1; i--)
        if (i % block)
            suf[i] = max(suf[i + 1], a[i]);
        else
            suf[i] = a[i];
}
int query(int l, int r) {
    int e = log2(r - l - 1);
    int res = max(st[l + 1][e], st[r - (1 << e)][e]);
    return res;
}
int main() {
    scanf("%d%d%d", &n, &m, &s);
    srand(s);
    block = sqrt(n);
    for (int i = 1; i <= n; i++) {
        int j = getpos(i);
        a[i] = read();
        st[j][0] = max(a[i], st[j][0]);
    }
    init();
    for (int x, y; m; m--) {
        x = read() % n + 1, y = read() % n + 1;
        if (x > y)
            swap(x, y);
        int l = getpos(x), r = getpos(y);
        int mx = max(suf[x], pre[y]);
        if (l == r)
            ans += bf(x, y);
        else if (r - l == 1)
            ans += mx;
        else
            ans += max(mx, query(l, r));
    }
    printf("%llu\n", ans);
    return 0;
}

拓展:

刚才说这个算法在数据随机的情况下是对的。

但实际上数据不随机也不太可能被卡。

要卡掉这个算法,就肯定要让区间尽可能在块内,让复杂度为 \(Olen)\)。

咕咕咕

上一篇:小样本点击率纠正-威尔逊(Wilson)区间


下一篇:Linux C 编程学习第四天_结构体&数据抽象