BZOJ4241 历史研究 【回滚莫队】

题目描述:给出一个长度为(n)的数组,每次询问区间 ([l,r]),求 (maxlimits_{x}x*cnt_x),其中 (cnt_x) 表示 (x) 在区间 ([l,r]) 的出现次数。

数据范围:(nle 10^5,a_ile 10^9)

分块也可以做到 (O(nsqrt{n})),但是空间也是 (O(nsqrt{n}))。使用回滚莫队可以在 (O(n)) 空间内解决。

首先上来离散化,然后考虑莫队。发现这个东西增加一个数是 (O(1)) 的,但删除一个数至少要 (O(log n)) 才能做,所以我们考虑只增。首先询问按照普通莫队的方法排序,然后处理 (l) 在一块,而且 (r) 递增的询问。设块的右端点为 (mid),则 ((mid,r]) 可以只添加数来解决,而 ([l,mid]) 这一段直接暴力(长度 (sqrt{n}))。总时间复杂度是 (O(nsqrt{n})) 的。

于是像区间众数这种东西也可以用回滚莫队做了。

code ```cpp #include #define Rint register int using namespace std; typedef long long LL; const int N = 100003; int n, m, blo, x[N], val[N], len, ql, qr, mid; LL cnt[N], qans, ans[N], tt[N]; struct Query { int l, r, id; inline bool operator < (const Query &o) const { if(l / blo != o.l / blo) return l / blo < o.l / blo; return r < o.r; } } q[N]; inline void add(int x){qans = max(qans, cnt[x] += val[x]);} inline void del(int x){cnt[x] -= val[x];} int main(){ scanf("%d%d", &n, &m); blo = sqrt(n); for(Rint i = 1;i <= n;i ++) scanf("%d", x + i), val[i] = x[i]; sort(val + 1, val + n + 1); len = unique(val + 1, val + n + 1) - val - 1; for(Rint i = 1;i <= n;i ++) x[i] = lower_bound(val + 1, val + len + 1, x[i]) - val; for(Rint i = 1;i <= m;i ++) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i; sort(q + 1, q + m + 1); for(Rint i = 1;i <= m;i ++){ if(i == 1 || q[i].l / blo != q[i - 1].l / blo){ memset(cnt, 0, sizeof cnt); qans = 0; ql = qr = mid = min(n, (q[i].l / blo + 1) * blo); } if(q[i].l / blo == q[i].r / blo){ LL res = 0; for(Rint j = q[i].l;j <= q[i].r;j ++) res = max(res, tt[x[j]] += val[x[j]]); ans[q[i].id] = res; for(Rint j = q[i].l;j <= q[i].r;j ++) tt[x[j]] = 0; continue; } while(qr <= q[i].r) add(x[qr]), ++ qr; LL tmp = qans; while(ql > q[i].l) -- ql, add(x[ql]); ans[q[i].id] = qans; while(ql < mid) del(x[ql]), ++ ql; qans = tmp; } for(Rint i = 1;i <= m;i ++) printf("%lld ", ans[i]); } ```
原文地址:https://www.cnblogs.com/AThousandMoons/p/11813002.html