BZOJ4241 历史研究 【回滚莫队】

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

数据范围:\(n\le 10^5,a_i\le 10^9\)

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

首先上来离散化,然后考虑莫队。发现这个东西增加一个数是 \(O(1)\) 的,但删除一个数至少要 \(O(\log n)\) 才能做,所以我们考虑只增。首先询问按照普通莫队的方法排序,然后处理 \(l\) 在一块,而且 \(r\) 递增的询问。设块的右端点为 \(mid\),则 \((mid,r]\) 可以只添加数来解决,而 \([l,mid]\) 这一段直接暴力(长度 \(\sqrt{n}\))。总时间复杂度是 \(O(n\sqrt{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\n", ans[i]); } ```
posted @ 2019-11-07 16:32  mizu164  阅读(150)  评论(0编辑  收藏  举报