You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardexpand all lines: content/6_Advanced/Offline_Del.mdx
+59-68
Original file line number
Diff line number
Diff line change
@@ -19,14 +19,14 @@ deleting from a data structure while only using insertion operations.
19
19
20
20
<Warningtitle="Watch Out!">
21
21
22
-
Because path compression is amortized, it does not guarantee
22
+
Because path compression is amortized, it does not guarantee
23
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
24
25
25
</Warning>
26
26
27
27
### Implementation
28
28
29
-
Adding on to usual DSU, we can store the parent
29
+
Adding on to usual DSU, we can store the parent
30
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
31
32
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.
@@ -40,7 +40,7 @@ struct DSU {
40
40
vector<int>p, sz;
41
41
42
42
// stores previous unites
43
-
vector<pair<int&, int>>history;
43
+
vector<pair<int&, int>>history;
44
44
45
45
DSU(intn) {
46
46
p.resize(n);
@@ -53,13 +53,13 @@ struct DSU {
53
53
voidunite(inta, intb) {
54
54
a=get(a);
55
55
b=get(b);
56
-
if(a == b) return;
56
+
if(a == b) return;
57
57
if (sz[a] < sz[b]) { swap(a, b); }
58
-
58
+
59
59
// save this unite operation
60
60
history.push_back({sz[a], sz[a]});
61
61
history.push_back({p[b], p[b]});
62
-
62
+
63
63
p[b] = a;
64
64
sz[a] += sz[b];
65
65
}
@@ -104,11 +104,11 @@ These types of problems involve determining whether pairs of nodes are in the sa
104
104
105
105
<FocusProblemproblem="sam" />
106
106
107
-
## Solution-VertexAddComponentSum
107
+
## Solution-VertexAddComponentSum
108
108
109
-
Fordynamicconnectivityproblems, 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.
109
+
Fordynamicconnectivityproblems, 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.
110
110
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.
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.
112
112
113
113
See the code below for implementation details. Note that similar to unite operations, we can also perform and undo operations of type $2$!
114
114
@@ -123,10 +123,10 @@ using ll = long long;
123
123
124
124
struct DSU {
125
125
vector<ll> p, sz, sum;
126
-
126
+
127
127
// stores all history info related to merges
128
-
vector<pair<ll&, ll>> history;
129
-
128
+
vector<pair<ll&, ll>> history;
129
+
130
130
DSU(int n) {
131
131
p.resize(n);
132
132
sz.resize(n, 1);
@@ -139,9 +139,9 @@ struct DSU {
139
139
void unite(int a, int b) {
140
140
a = get(a);
141
141
b = get(b);
142
-
if(a == b) return;
142
+
if(a == b) return;
143
143
if (sz[a] < sz[b]) { swap(a, b); }
144
-
144
+
145
145
// add to history
146
146
history.push_back({p[b], p[b]});
147
147
history.push_back({sz[a], sz[a]});
@@ -151,19 +151,17 @@ struct DSU {
151
151
sz[a] += sz[b];
152
152
sum[a] += sum[b];
153
153
}
154
-
155
-
void add(int x, ll v){
154
+
155
+
void add(int x, ll v){
156
156
x = get(x);
157
157
history.push_back({sum[x], sum[x]});
158
158
sum[x] += v;
159
159
}
160
-
161
-
int snapshot(){
162
-
return history.size();
163
-
}
160
+
161
+
int snapshot() { return history.size(); }
164
162
165
163
void rollback(int until) {
166
-
while(snapshot() > until){
164
+
while(snapshot() > until){
167
165
history.back().first = history.back().second;
168
166
history.pop_back();
169
167
}
@@ -174,15 +172,15 @@ const int MAXN = 3e5 + 5;
174
172
175
173
DSU dsu(MAXN);
176
174
177
-
struct query{
175
+
struct query{
178
176
int t, u, v, x;
179
177
};
180
178
181
179
vector<query> tree[MAXN * 4];
182
180
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){
181
+
void update(query &q, int v, int l, int r, int L, int R){
182
+
if(l > R || r < L) return;
183
+
if(l <= L && r >= R){
186
184
tree[v].push_back(q);
187
185
return;
188
186
}
@@ -191,27 +189,19 @@ void update(query& q, int v, int l, int r, int L, int R){
191
189
update(q, v * 2 + 1, l, r, m + 1, R);
192
190
}
193
191
194
-
195
-
void dfs(int v, int l, int r, vector<ll>& ans){
192
+
void dfs(int v, int l, int r, vector<ll> &ans) {
196
193
int snapshot = dsu.snapshot();
197
194
// 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){
195
+
for (query &q : tree[v]) {
196
+
if (q.t == 1) { dsu.unite(q.u, q.v); }
197
+
if (q.t == 2) { dsu.add(q.v, q.x); }
198
+
}
199
+
if (l == r) {
207
200
// 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
-
}
201
+
for (query &q : tree[v]) {
202
+
if (q.t == 3) { ans[l] = dsu.sum[dsu.get(q.v)]; }
212
203
}
213
-
}
214
-
else{
204
+
} else {
215
205
// go deeper into the tree
216
206
int m = (l + r) / 2;
217
207
dfs(2 * v, l, m, ans);
@@ -222,58 +212,59 @@ void dfs(int v, int l, int r, vector<ll>& ans){
222
212
}
223
213
224
214
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;
215
+
cin.tie(0)->sync_with_stdio(0);
216
+
int n, q;
217
+
cin >> n >> q;
218
+
for (int i = 0; i < n; i++) {
219
+
ll x;
220
+
cin >> x;
229
221
dsu.sum[i] = x;
230
222
}
231
223
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);
224
+
for (int i = 0; i < q; i++) {
225
+
int t;
226
+
cin >> t;
227
+
if (t == 0) {
228
+
int u, v;
229
+
cin >> u >> v;
230
+
if (u > v) swap(u, v);
237
231
// store index this edge is added, marks beginning of interval
238
232
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);
233
+
} else if (t == 1) {
234
+
int u, v;
235
+
cin >> u >> v;
236
+
if(u > v) swap(u, v);
243
237
query cur_q = {1, u, v};
244
238
// add all edges that are deleted to interval [index added, i - 1]
0 commit comments