Skip to content

Commit 38db918

Browse files
authored
owo
1 parent 7750c0e commit 38db918

File tree

1 file changed

+241
-46
lines changed

1 file changed

+241
-46
lines changed

content/6_Advanced/Offline_Del.mdx

+241-46
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,279 @@
11
---
22
id: offline-del
33
title: 'Offline Deletion'
4-
author: Benjamin Qi, Siyong Huang
4+
author: Benjamin Qi, Siyong Huang, Chongtian Ma
55
prerequisites:
66
- dsu
77
description: 'Erasing from non-amortized insert-only data structures.'
88
frequency: 0
99
---
1010

11-
## Offline Deleting from a Data Structure
12-
1311
Using a persistent data structure or rollbacking, you are able to simulate
1412
deleting from a data structure while only using insertion operations.
1513

16-
<Resources>
17-
<Resource
18-
source="CP-Algorithms"
19-
title="Deleting from a data structure in O(T(n) log n)"
20-
url="https://cp-algorithms.com/data_structures/deleting_in_log_n.html"
21-
starred
22-
>
23-
includes code (but no explanation) for dynamic connectivity
24-
</Resource>
25-
</Resources>
14+
## DSU With Rollback
15+
16+
**DSU with Rollback** is an extension to DSU that saves merges and can undo the previous $k$ merges.
17+
18+
<Problems problems="rollback" />
19+
20+
<Warning title="Watch Out!">
21+
22+
Because path compression is amortized, it does not guarantee
23+
$\mathcal{O}(N \log^2 N)$ runtime. You _must_ use merging by rank. An explanation is given [here](https://codeforces.com/blog/entry/90340?#comment-787571).
24+
25+
</Warning>
26+
27+
### Implementation
28+
29+
Adding on to usual DSU, we can store the parent
30+
and sizes of nodes that are being merged before each merge. This allows us revert each node to their parents before the merge so that the rollback function can use the information to undo the merges.
31+
32+
We can store each state of the DSU using an integer, captured by the snapshot function which returns the number of old merges that has not been rolled back. It's similar taking a picture of something, and years later going back into your photo album and scrolling up until you find this picture.
33+
34+
<LanguageSection>
35+
36+
<CPPSection>
37+
38+
```cpp
39+
struct DSU {
40+
vector<int> p, sz;
41+
42+
// stores previous unites
43+
vector<pair<int&, int>> history;
44+
45+
DSU(int n) {
46+
p.resize(n);
47+
sz.resize(n, 1);
48+
iota(p.begin(), p.end(), 0);
49+
}
50+
51+
int get(int x) { return x == p[x] ? x : get(p[x]); }
52+
53+
void unite(int a, int b) {
54+
a = get(a);
55+
b = get(b);
56+
if(a == b) return;
57+
if (sz[a] < sz[b]) { swap(a, b); }
58+
59+
// save this unite operation
60+
history.push_back({sz[a], sz[a]});
61+
history.push_back({p[b], p[b]});
62+
63+
p[b] = a;
64+
sz[a] += sz[b];
65+
}
66+
67+
int snapshot() { return psnap.size(); }
68+
69+
void rollback(int until) {
70+
while (snapshot() > until) {
71+
history.back().first = history.back().second;
72+
history.pop_back();
73+
}
74+
}
75+
};
76+
```
77+
78+
</CPPSection>
79+
80+
</LanguageSection>
2681

2782
## Dynamic Connectivity
2883

84+
**Dynamic Connectivity** is the most common problem using the deleting trick.
85+
These types of problems involve determining whether pairs of nodes are in the same connected component while edges are being inserted and removed.
86+
2987
<Resources>
88+
<Resource source="CP-Algorithms"
89+
title="Deleting from a data structure in O(T(n) log n)"
90+
url="https://cp-algorithms.com/data_structures/ deleting_in_log_n.html"
91+
starred = "true"
92+
/>
3093
<Resource source="GCP" title="15.5.4 - Dynamic Connectivity"/>
94+
<Resource source="CF"
95+
title="Dynamic Connectivity Contest"
96+
url="https://codeforces.com/gym/100551"
97+
starred="true"
98+
/>
99+
<Resource source="Vivek Gupta"
100+
title="Dynamic Connectivity Video Explanation"
101+
url="https://www.youtube.com/watch?v=7gqFcunrFH0"
102+
/>
31103
</Resources>
32104

33-
**Dynamic Connectivity** is the most common problem using the deleting trick.
34-
The problem is to determine whether pairs of nodes are in the same connected
35-
component while edges are being inserted and removed.
105+
<FocusProblem problem="sam" />
36106

37-
<Problems problems="sam" />
107+
## Solution - Vertex Add Component Sum
38108

39-
### DSU With Rollback
109+
For dynamic connectivity problems, we say for every query, there's an interval $[l, r]$ where it's active. Obviously, for each edge add/remove query, $l = $ (the index of the query which adds the edge), and $r = $ (the index of the query which removes the edge) $-1$. If an edge never gets removed, then $r = q - 1$. Note that we assign intervals such that for queries outside the interval, they are completely unaffected by this query. We can use similar reasoning to construct intervals for type $2$ and $3$ queries.
40110

41-
**DSU with rollback** is a subproblem required to solve the above task.
111+
We can now construct a query tree. If our interval is encapsulated by the the tree's interval, then we can add our query to the node corresponding to the interval. When answering queries, as we enter the interval, we can process all the operations inside the interval. As we exit the interval, we need to undo them. If we are at a leaf, we can answer type $3$ queries since we have processed all queries outside this interval $[i, i]$. Since we are processing intevals by halves each time, the depth is at most $\log n$, similar to divide and conquer.
42112

43-
<Problems problems="rollback" />
113+
See the code below for implementation details. Note that similar to unite operations, we can also perform and undo operations of type $2$!
44114

45-
<IncompleteSection>
115+
<LanguageSection>
46116

47-
explanation? check Guide to CP?
117+
<CPPSection>
48118

49-
</IncompleteSection>
119+
```cpp
120+
#include <bits/stdc++.h>
121+
using namespace std;
122+
using ll = long long;
50123
51-
<Warning title="Watch Out!">
124+
struct DSU {
125+
vector<ll> p, sz, sum;
126+
127+
// stores all history info related to merges
128+
vector<pair<ll&, ll>> history;
129+
130+
DSU(int n) {
131+
p.resize(n);
132+
sz.resize(n, 1);
133+
sum.resize(n);
134+
iota(p.begin(), p.end(), 0);
135+
}
52136
53-
Because path compression is amortized, it does not guarauntee
54-
$\mathcal{O}(N \log^2 N)$ runtime. You _must_ use merging by rank.
137+
int get(int x) { return (p[x] == x) ? x : get(p[x]); }
55138
56-
</Warning>
139+
void unite(int a, int b) {
140+
a = get(a);
141+
b = get(b);
142+
if(a == b) return;
143+
if (sz[a] < sz[b]) { swap(a, b); }
144+
145+
// add to history
146+
history.push_back({p[b], p[b]});
147+
history.push_back({sz[a], sz[a]});
148+
history.push_back({sum[a], sum[a]});
57149
58-
### Implementation
150+
p[b] = a;
151+
sz[a] += sz[b];
152+
sum[a] += sum[b];
153+
}
154+
155+
void add(int x, ll v){
156+
x = get(x);
157+
history.push_back({sum[x], sum[x]});
158+
sum[x] += v;
159+
}
160+
161+
int snapshot(){
162+
return history.size();
163+
}
59164
60-
<LanguageSection>
165+
void rollback(int until) {
166+
while(snapshot() > until){
167+
history.back().first = history.back().second;
168+
history.pop_back();
169+
}
170+
}
171+
};
61172
62-
<CPPSection>
173+
const int MAXN = 3e5 + 5;
63174
64-
<!-- verified at https://codeforces.com/contest/1217/submission/62335472 -->
175+
DSU dsu(MAXN);
65176
66-
```cpp
67-
int p[MN], r[MN];
68-
int *t[MN * 40], v[MN * 40], X;
69-
int setv(int *a, int b) {
70-
if (*a != b) t[X] = a, v[X] = *a, *a = b, ++X;
71-
return b;
177+
struct query{
178+
int t, u, v, x;
179+
};
180+
181+
vector<query> tree[MAXN * 4];
182+
183+
void update(query& q, int v, int l, int r, int L, int R){
184+
if(l > R || r < L) return;
185+
if(l <= L && r >= R){
186+
tree[v].push_back(q);
187+
return;
188+
}
189+
int m = (L + R) / 2;
190+
update(q, v * 2, l, r, L, m);
191+
update(q, v * 2 + 1, l, r, m + 1, R);
72192
}
73-
void rollback(int x) {
74-
for (; X > x;) --X, *t[X] = v[X];
193+
194+
195+
void dfs(int v, int l, int r, vector<ll>& ans){
196+
int snapshot = dsu.snapshot();
197+
// perform all available operations upon entering
198+
for(query& q: tree[v]){
199+
if(q.t == 1){
200+
dsu.unite(q.u, q.v);
201+
}
202+
if(q.t == 2){
203+
dsu.add(q.v, q.x);
204+
}
205+
}
206+
if(l == r){
207+
// answer type 3 query if we have one
208+
for(query& q: tree[v]){
209+
if(q.t == 3){
210+
ans[l] = dsu.sum[dsu.get(q.v)];
211+
}
212+
}
213+
}
214+
else{
215+
// go deeper into the tree
216+
int m = (l + r) / 2;
217+
dfs(2 * v, l, m, ans);
218+
dfs(2 * v + 1, m + 1, r, ans);
219+
}
220+
// undo operations upon exiting
221+
dsu.rollback(snapshot);
75222
}
76-
int find(int n) { return p[n] ? find(p[n]) : n; }
77-
bool merge(int a, int b) {
78-
a = find(a), b = find(b);
79-
if (a == b) return 0;
80-
if (r[b] > r[a]) std::swap(a, b);
81-
if (r[a] == r[b]) setv(r + a, r[a] + 1);
82-
return setv(p + b, a), 1;
223+
224+
int main() {
225+
cin.tie(0) -> sync_with_stdio(0);
226+
int n, q; cin >> n >> q;
227+
for(int i = 0; i < n; i++){
228+
ll x; cin >> x;
229+
dsu.sum[i] = x;
230+
}
231+
map<pair<int, int>, int> index_added;
232+
for(int i = 0; i < q; i++){
233+
int t; cin >> t;
234+
if(t == 0){
235+
int u, v; cin >> u >> v;
236+
if(u > v) swap(u, v);
237+
// store index this edge is added, marks beginning of interval
238+
index_added[{u, v}] = i;
239+
}
240+
else if(t == 1){
241+
int u, v; cin >> u >> v;
242+
if(u > v) swap(u, v);
243+
query cur_q = {1, u, v};
244+
// add all edges that are deleted to interval [index added, i - 1]
245+
update(cur_q, 1, index_added[{u, v}], i - 1, 0, q - 1);
246+
index_added[{u, v}] = -1;
247+
}
248+
else if(t == 2){
249+
int v, x; cin >> v >> x;
250+
query cur_q = {2, -1, v, x};
251+
// add all sum queries to interval [i, q - 1]
252+
update(cur_q, 1, i, q - 1, 0, q - 1);
253+
}
254+
else{
255+
int v; cin >> v;
256+
query cur_q = {3, -1, v};
257+
// add all output queries to interval [i, i]
258+
update(cur_q, 1, i, i, 0, q - 1);
259+
}
260+
}
261+
// add all edges that are not deleted to interval [index added, q - 1]
262+
for(auto [edge, index]: index_added){
263+
if(index != -1){
264+
query cur_q = {1, edge.first, edge.second};
265+
update(cur_q, 1, index, q - 1, 0, q - 1);
266+
}
267+
}
268+
vector<ll> ans(q, -1);
269+
dfs(1, 0, q - 1, ans);
270+
for(int i = 0; i < q; i++){
271+
if(ans[i] != -1){
272+
cout << ans[i] << "\n";
273+
}
274+
}
83275
}
276+
84277
```
85278

86279
</CPPSection>
@@ -93,6 +286,8 @@ bool merge(int a, int b) {
93286

94287
<!-- ## Dynamic Insertion
95288

289+
i have no idea what this is i literally cant find it anywhere
290+
96291
mention sqrt
97292

98293
(online Aho-Corasick)

0 commit comments

Comments
 (0)