[回滚莫队] 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;
}