[回滚莫队] AtCoder 歴史の研究

题目大意

给定长为 \(n(1\leq n\leq 10^5)\) 的序列 \(\{a_n\}\),其中 \(1\leq a_i\leq 10^9\)

\(q(1\leq q\leq 10^5)\) 个询问,每次询问序列的一个区间 \([L,R]\) 内的一个数乘以它在这个区间内出现的次数的最大值。

题解

发现询问可以离线,如果用普通莫队来做,可以发现加点很容易,每加入一个数,更新一下 \(\mathrm{maxval}\) 即可。但是删点很困难,因为之前的 \(\mathrm{maxval}\) 被覆盖了,难以回退。此时就可以使用回滚莫队来做。

将整个序列按根号大小分块,将所有询问按左端点所属块为第一关键字升序排序,按右端点为第二关键字升序排序。每次我们对所有左端点所属块相同的询问一起处理,这样询问右端点一定是单调递增的。现在考察这样的一组询问:

设当前组询问的左端点全部在块 \(x\) 内,则设 \(L=\mathrm{Right}(x)+1,R=\mathrm{Right}(x)\)

若询问的左右端点同属同一块中,则暴力处理该询问,单次 \(O(\sqrt n)\)

否则,由于同一组内询问的右端点单调递增,我们先把 \(R\) 不断移到当前询问的右端点,最多移动 \(O(n)\) 次。但此时左端点可能是无序的,我们先记录下原先的 \(\mathrm{maxval}\),然后把 \(L\) 移动到询问的位置,记录此询问的答案,然后将 \(L\) 回滚到 \(\mathrm{Right}(x)+1\),恢复到上一次的 \(\mathrm{maxval}\),由于 \(L\) 是在块内移动,此处 \(L\) 移动的是 \(O(\sqrt n)\) 的。

暴力计算块内询问和移动 \(L\) 对时间复杂度的贡献是 \(q\sqrt n\)。由于所有询问最多分为 \(\sqrt n\) 组,因此移动 \(R\) 对时间复杂度带来的贡献是 \(O(n\sqrt n)\)。因此回滚莫队的时间复杂度为 \(O((n+q)\sqrt n)\)

此题注意还要进行离散化。

Code

#include <bits/stdc++.h>
using namespace std;

#define LL long long

template<typename elemType>
inline void Read(elemType& T) {
    elemType X = 0, w = 0; char ch = 0;
    while (!isdigit(ch)) { w |= ch == '-'; ch = getchar(); }
    while (isdigit(ch)) X = (X << 3) + (X << 1) + (ch ^ 48), ch = getchar();
    T = (w ? -X : X);
}

int BLOCK_SIZE, BLOCK_NUM;
int a[100010], cnt[100010], belong[100010], id[100010], pos[100010];
LL ans[100010];
pair<int, int> ask[100010];
int N, Q, L, R;
LL mxval;

vector<int> HashData;

void Discretization() {
    HashData.push_back(-(1 << 30));
    for (int i = 1; i <= N; ++i)
        HashData.push_back(a[i]);
    sort(HashData.begin(), HashData.end());
    HashData.erase(unique(HashData.begin(), HashData.end()), HashData.end());
    for (int i = 1; i <= N; ++i)
        pos[i] = lower_bound(HashData.begin(), HashData.end(), a[i]) - HashData.begin();
}

bool cmp(int a, int b) {
    if (belong[ask[a].first] == belong[ask[b].first])
        return ask[a].second < ask[b].second;
    return belong[ask[a].first] < belong[ask[b].first];
}

void BuildBlock() {
    BLOCK_SIZE = sqrt(N);
    BLOCK_NUM = (N - 1) / BLOCK_SIZE + 1;
    for (int i = 1; i <= BLOCK_NUM; ++i)
        for (int j = (i - 1) * BLOCK_SIZE + 1; j <= min(N, i * BLOCK_SIZE); ++j)
            belong[j] = i;
}

void Add(int x) { mxval = max(mxval, 1LL * a[x] * (++cnt[pos[x]])); }
void Clear() { for (int i = L; i <= R; ++i) cnt[pos[i]] = 0; }
LL Violent(int l, int r) {
    LL res = 0;
    for (int i = l; i <= r; ++i) ++cnt[pos[i]];
    for (int i = l; i <= r; ++i) {
        if (cnt[pos[i]]) {
            res = max(res, 1LL * a[i] * cnt[pos[i]]);
            cnt[pos[i]] = 0;
        }
    }
    return res;
}

int main() {
    Read(N); Read(Q);
    BuildBlock();
    for (int i = 1; i <= N; ++i)
        Read(a[i]);
    Discretization();
    for (int i = 1; i <= Q; ++i) {
        id[i] = i;
        Read(ask[i].first);
        Read(ask[i].second);
    }
    sort(id + 1, id + Q + 1, cmp);
    for (int i = 1; i <= Q; ++i) {
        int l = ask[id[i]].first, r = ask[id[i]].second;
        if (belong[l] != belong[ask[id[i - 1]].first]) {
            Clear();
            R = belong[l] * BLOCK_SIZE; L = R + 1; mxval = 0;
        }
        if (belong[l] == belong[r])
            ans[id[i]] = Violent(l, r);
        else {
            while (R < r) Add(++R);
            LL temp = mxval;
            for (int j = L - 1; j >= l; --j)
                mxval = max(mxval, 1LL * a[j] * (++cnt[pos[j]]));
            ans[id[i]] = mxval;
            mxval = temp;
            for (int j = L - 1; j >= l; --j)
                --cnt[pos[j]];
        }
    }
    for (int i = 1; i <= Q; ++i)
        printf("%lld\n", ans[i]);

    return 0;
}
posted @ 2021-05-14 11:25  AE酱  阅读(114)  评论(3编辑  收藏  举报