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
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
+
<Problemsproblems="rollback" />
19
+
20
+
<Warningtitle="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
+
structDSU{
40
+
vector<int>p, sz;
41
+
42
+
// stores previous unites
43
+
vector<pair<int&, int>>history;
44
+
45
+
DSU(intn) {
46
+
p.resize(n);
47
+
sz.resize(n, 1);
48
+
iota(p.begin(), p.end(), 0);
49
+
}
50
+
51
+
intget(intx) { return x==p[x] ?x:get(p[x]); }
52
+
53
+
voidunite(inta, intb) {
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>
26
81
27
82
## Dynamic Connectivity
28
83
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
+
29
87
<Resources>
88
+
<Resource source="CP-Algorithms"
89
+
title="Deleting from a data structure in O(T(n) log n)"
**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
+
<FocusProblemproblem="sam" />
36
106
37
-
<Problemsproblems="sam" />
107
+
## Solution-VertexAddComponentSum
38
108
39
-
### DSU With Rollback
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.
40
110
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.
42
112
43
-
<Problemsproblems="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$!
44
114
45
-
<IncompleteSection>
115
+
<LanguageSection>
46
116
47
-
explanation? check Guide to CP?
117
+
<CPPSection>
48
118
49
-
</IncompleteSection>
119
+
```cpp
120
+
#include <bits/stdc++.h>
121
+
using namespace std;
122
+
using ll = long long;
50
123
51
-
<Warningtitle="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
+
}
52
136
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]); }
0 commit comments