回滚莫队 学习笔记
板子题交
update on 2023/8/28 终于不 UKE 了。
回滚莫队
有些题看起来像个莫队,想着想着发现 add 操作很容易实现,而 del 操作怎么都想不出来,或者是 del 操作时间复杂度不是
AT_joisc2014_c 歴史の研究
给你一个长度为
sol:
从莫队的角度出发,首先我们尝试实现 add 和 del 函数。
add 函数:在当前区间内增加一个数并更新答案。加入数
del 函数:?好像很难
那怎么办?不要删除操作就行了。
首先还是很正常的莫队分块+排序。
然后按顺序处理询问:
-
如果询问的左端点跳到了新的一块,那么将左右指针扔到当前块的右边形成空集。
-
如果询问的左右端点在同一块内,直接暴力即可。
-
往右跳右指针到右端点。
-
然后往左跳左指针到左端点。
-
到位后回答询问。
-
撤销之前的修改,把指针挪回去。
注意这里的撤销并不是删除,撤销的方法很多,例如这题就是直接记录原位的答案,然后再把
这样我们就做到了不带删除完成莫队的内容。
时间复杂度方面,不是很会证明。设询问数量是
code:
#include<iostream> #include<fstream> #include<algorithm> #include<cmath> #include<cstring> #define int long long using namespace std; int n, Q, N, T, bl, tmp; int a[100005], b[100005]; int L[100005], R[100005]; int belong[100005]; int ans[100005]; struct Query{ int l, r, id; }q[100005]; bool cmp(Query u, Query v){ return belong[u.l] == belong[v.l] ? u.r < v.r : u.l < v.l; } int cn[100005], cnt[100005]; int solve(int l, int r){ int ans = 0; for(int i = l; i <= r; i ++) cn[a[i]] ++; for(int i = l; i <= r; i ++) ans = max(ans, cn[a[i]] * b[a[i]]); for(int i = l; i <= r; i ++) cn[a[i]] --; return ans; } int add(int x){ cnt[a[x]] ++; tmp = max(tmp, cnt[a[x]] * b[a[x]]); return 0; } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n >> Q; for(int i = 1; i <= n; i ++) cin >> a[i], b[i] = a[i]; for(int i = 1; i <= Q; i ++) cin >> q[i].l >> q[i].r, q[i].id = i; sort(b + 1, b + n + 1); N = unique(b + 1, b + n + 1) - b - 1; for(int i = 1; i <= n; i ++) a[i] = lower_bound(b + 1, b + N + 1, a[i]) - b; T = sqrt(n), bl = n / T; for(int i = 1; i <= n; i ++) L[i] = R[i - 1] + 1, R[i] = L[i] + T - 1; if(R[bl] < n) ++ bl, L[bl] = R[bl - 1] + 1, R[bl] = n; for(int i = 1; i <= n; i ++) belong[i] = (i - 1) / T + 1; sort(q + 1, q + Q + 1, cmp); for(int i = 1, r = 0, l = 0, nw = 1; i <= bl; i ++){ memset(cnt, 0, sizeof(cnt)); r = R[i]; tmp = 0; while(belong[q[nw].l] == i){ l = R[i] + 1; if(q[nw].r - q[nw].l <= T){ ans[q[nw].id] = solve(q[nw].l, q[nw].r); ++ nw; continue; } while(q[nw].r > r) add(++ r); int ret = tmp; while(l > q[nw].l) add(-- l); ans[q[nw].id] = tmp; tmp = ret; while(l <= R[i]) cnt[a[l ++]] --; ++ nw; } } for(int i = 1; i <= Q; i ++) cout << ans[i] << "\n"; return 0; }
另外一直在 UKE 我也不知道对不对反正样例是过了。
update on 2023/8/28: Accept.
【模板】回滚莫队&不删除莫队
给定一个序列,多次询问一段区间
序列中两个元素的间隔距离指的是两个元素下标差的绝对值。
sol:
这个至少能交。
思路一样,只需要处理下 add 和 solve 函数,然后写一下 rollback 还原就行了。
-
add:我的做法常数比较大。直接记录每个数在当前区间的最左边的出现位置和最右边的出现位置即可,然后相减更新答案。
-
solve:暴力而已,按照上面的思路暴力下即可。
-
rollback:在向左扩展左指针的时候开一个栈记录上一个修改的位置,然后在 rollback 的时候依次将元素还原、退栈即可。
然后就是一个回滚莫队板子了。
code
#include<iostream> #include<fstream> #include<algorithm> #include<cmath> #include<cstring> using namespace std; const int inf = 0x3f3f3f3f; int n, Q, N, T, bl, tmp; int a[200005], b[200005]; int L[200005], R[200005]; int belong[200005]; int ans[200005]; struct Query{ int l, r, id; }q[200005]; bool cmp(Query u, Query v){ return belong[u.l] == belong[v.l] ? u.r < v.r : u.l < v.l; } int cn1[200005], cn2[200005], cnt1[200005], cnt2[200005], tp; pair<int, pair<int, int> > st[200005]; int solve(int l, int r){ int ans = 0; for(int i = l; i <= r; i ++) cn1[a[i]] = min(cn1[a[i]], i), cn2[a[i]] = max(cn2[a[i]], i); for(int i = l; i <= r; i ++) ans = max(ans, cn2[a[i]] - cn1[a[i]]); for(int i = l; i <= r; i ++) cn1[a[i]] = inf, cn2[a[i]] = 0; return ans; } int add(int x){ cnt1[a[x]] = min(cnt1[a[x]], x); cnt2[a[x]] = max(cnt2[a[x]], x); tmp = max(tmp, cnt2[a[x]] - cnt1[a[x]]); return 0; } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n; memset(cn1, 0x3f, sizeof(cn1)); for(int i = 1; i <= n; i ++) cin >> a[i], b[i] = a[i]; cin >> Q; for(int i = 1; i <= Q; i ++) cin >> q[i].l >> q[i].r, q[i].id = i; sort(b + 1, b + n + 1); N = unique(b + 1, b + n + 1) - b - 1; for(int i = 1; i <= n; i ++) a[i] = lower_bound(b + 1, b + N + 1, a[i]) - b; T = sqrt(n), bl = n / T; for(int i = 1; i <= n; i ++) L[i] = R[i - 1] + 1, R[i] = L[i] + T - 1; if(R[bl] < n) ++ bl, L[bl] = R[bl - 1] + 1, R[bl] = n; for(int i = 1; i <= n; i ++) belong[i] = (i - 1) / T + 1; sort(q + 1, q + Q + 1, cmp); for(int i = 1, r = 0, l = 0, nw = 1; i <= bl; i ++){ memset(cnt1, 0x3f, sizeof(cnt1)); memset(cnt2, 0, sizeof(cnt2)); r = R[i]; tmp = 0; while(belong[q[nw].l] == i){ l = R[i] + 1; if(q[nw].r - q[nw].l <= T){ ans[q[nw].id] = solve(q[nw].l, q[nw].r); ++ nw; continue; } while(q[nw].r > r) add(++ r); int ret = tmp; while(l > q[nw].l) st[++ tp].first = a[-- l], st[tp].second.first = cnt1[a[l]], st[tp].second.second = cnt2[a[l]], add(l); ans[q[nw].id] = tmp; tmp = ret; l = R[i] + 1; while(tp) cnt1[st[tp].first] = st[tp].second.first, cnt2[st[tp].first] = st[tp].second.second, tp --; ++ nw; } } for(int i = 1; i <= Q; i ++) cout << ans[i] << "\n"; return 0; }
p.s. Rollback 的时候要注意你 roll 了个什么东西。直接把
Rmq Problem / mex
有一个长度为
sol:
不扩大区间的回滚莫队。(或者叫只删莫队(自己取的)
好像可以值域分块但是我摆。
依然是考虑 add 和 del 函数。
-
del:记录一个桶,删除一个数时,如果这一次操作将这个数删完了,那就更新当前
。 -
add:思考好一会,发现很难实现快速增加一个数。
那可以同样的套路回滚。如果不能扩大区间,那就只用删除解决所有问题。
首先排序时,对于同一块的点,右端点从大到小排序。然后回滚莫队。
具体而言:
-
先暴力求出满集的答案,把桶更新好。
-
如果询问的左端点跳到了新的一块,那么右指针 rollback 到最右边形成满集,左指针 del 到当前块首。
-
如果询问的左右端点在同一块内,直接暴力即可。
-
往左跳右指针到右端点。
-
然后往右跳左指针到左端点。
-
到位后回答询问。
-
撤销之前的修改,把指针挪回去。
其实没什么不同。但是 rollback 的时候需要稍微注意些细节。在这题因为桶不会清空所以不能用 memset,而自然右指针移动到最右端只能用 rollback 的方式。
code
#include<iostream> #include<fstream> #include<algorithm> #include<cmath> using namespace std; int n, m; int a[200005]; int L[200005], R[200005]; int T, bl; int belong[200005]; struct Query{ int l, r, id; }q[200005]; int ans[200005]; bool cmp(Query u, Query v){ return belong[u.l] == belong[v.l] ? u.r > v.r : u.l < v.l; } int cn[200005]; int solve(int l, int r){ int ret = 0; for(int i = l; i <= r; i ++){ cn[a[i]] ++; if(a[i] == ret) while(cn[++ ret]); } for(int i = l; i <= r; i ++) cn[a[i]] --; return ret; } int cnt[200005]; int tmp; int del(int u){ cnt[a[u]] --; if(!cnt[a[u]]) tmp = min(tmp, a[u]); return 0; } int main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n >> m; for(int i = 1; i <= n; i ++) cin >> a[i], cnt[a[i]] ++; for(int i = 1; i <= m; i ++){ cin >> q[i].l >> q[i].r, q[i].id = i; } T = sqrt(n), bl = n / T; for(int i = 1; i <= bl; i ++) L[i] = R[i - 1] + 1, R[i] = L[i] + T - 1; if(R[bl] < n) bl ++, L[bl] = R[bl - 1] + 1, R[bl] = n; for(int i = 1; i <= bl; i ++) for(int j = L[i]; j <= R[i]; j ++) belong[j] = i; sort(q + 1, q + m + 1, cmp); tmp = solve(1, n); int t1 = tmp; for(int i = 1, l = 1, r = 0, nw = 1; i <= bl; i ++){ r = n; tmp = t1; while(l < L[i]) del(l ++); t1 = tmp; while(belong[q[nw].l] == i){ if(q[nw].r - q[nw].l <= T){ ans[q[nw].id] = solve(q[nw].l, q[nw].r); nw ++; continue; } while(q[nw].r < r) del(r --); int ret = tmp; while(l < q[nw].l) del(l ++); ans[q[nw].id] = tmp; tmp = ret; while(l > L[i]) cnt[a[-- l]] ++; nw ++; } while(r < n) cnt[a[++ r]] ++; } for(int i = 1; i <= m; i ++) cout << ans[i] << "\n"; return 0; }
本文作者:AzusidNya
本文链接:https://www.cnblogs.com/AzusidNya/p/17652889.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步