Hello, in this blog I want to introduce a simple way to online delete/remove an element from its union in O(1), with the cost of using more memory.
consider this is your usual DSU:
struct DSU{
int p[1000005], sz[1000005];
void init(int n){
for(int i = 1; i <= n; i ++) p[i] = i;
fill(sz + 1,sz + n + 1, 1);
}
int find(int u){
if(u == p[u]) return u;
return p[u] = find(p[u]);
}
void uni(int x, int y){
x = find(x), y = find(y);
if(x == y) return;
if(sz[x] < sz[y]) swap(x, y);
p[y] = x, sz[x] += sz[y];
}
} dsu;
Say, you have already a set A = {1, 2, 3, 4, 5}, and you need to remove 3 from A (for some reason). In short, turn {1, 2, 3, 4, 5} to {3} and {1, 2, 4, 5}. How do you do it? You just need to change p[3] to a new value!!!
But what if there are other elements (denote x) that have p[x] = 3? To solve that problem, we can instead of using an element to represent a set, but use numbers from n + 1 onward. By using separate numbers to represent a set, any changes to element u of set A will not affect any other elements in A.
the new DSU:
struct DSU{
vector <int> sz, p;
int timer;
void init(int n){
timer = n * 2;
p.resize(n * 2 + 1);
sz.resize(n * 2 + 1, 1);
for(int i = n + 1; i <= n * 2; i ++) p[i] = i;
for(int i = 1; i <= n; i ++) p[i] = i + n; // use i + n instead of i
}
int find(int u){
if(u == p[u]) return u;
return p[u] = find(p[u]);
}
void uni(int x, int y){
x = find(x), y = find(y);
if(x == y) return;
if(sz[x] < sz[y]) swap(x, y);
p[y] = x, sz[x] += sz[y];
}
void del(int u){
int pr = find(u);
if(sz[pr] == 1) return; // no need to delete u from its set if the set contains only u
sz[pr] --; // reduce size of old set
timer ++;
p.push_back(timer); // adding the new set to p and sz
sz.push_back(1);
p[u] = timer;
}
} dsu;
As you can see, I have edited init() and add a new function del(), but uni() and find() remain unchanged. To optimize the memory usage from O(n + m) to O(n * 2) = O(n) (m denotes the number of operations to the DSU), we can reuse any empty sets that are left behind after merging 2 sets, but will further complicates the DSU which destroys its main advantage of being simple to implement.
Note: I haven't found any implement of this technique online so I decided to write this blog. This is also my first blog ever on Codeforces, and it will be expected to have many mistakes (both in my code and in the blog), so please point out any mistakes you find in this blog, thank you in advance!!!








Auto comment: topic has been updated by monaxia (previous revision, new revision, compare).
Auto comment: topic has been updated by monaxia (previous revision, new revision, compare).
An alternative but equivalent way to do this would be to start with just the usual $$$n$$$. If you want to delete $$$i$$$, get rep and subtract 1 from its size then extend the DSU by 1 (create a new set) and return this new value. We can then keep track of the current name of each element externally. This is very similar to what you did, just separated out a bit.
I happened to have used what I described above to solve a problem the other day: 348549874.
This also works
Someone please give questions related to this topic.
https://open.kattis.com/problems/almostunionfind
this only works if $$$u$$$ is a leaf of the component you are deleting it from; minimal example where it does not work:
you could do this task with a small to large approach.
Wasn't making i point to i + n in init() eliminate this problem? That way all elements from 1 -> n will always be the leaves of the component even after merging.
The example actually does work, if you notice that the implementation given in the blog has sets indexed from $$$1$$$ to $$$n$$$, not $$$0$$$ to $$$n-1$$$.
In fact, I believe $$$u$$$ is guaranteed to be a leaf of the component you delete it from because both on initialisation and deletion each node from $$$1$$$ to $$$n$$$ is already given a parent, so it is never the root of a component, so it can never gain children.
yes you are right that is my bad, thanks for pointing it out!
EDIT 2: as sammyuri pointed out it does work
I wonder if there are any problems that can be solve with this
This doesn't preserve the DSU property as representing the connectivity of the underlying graph though, e.g.
dsu.uni(2,3); dsu.uni(1,2); dsu.del(2);Will still have 1 and 3 as in the same component. To solve this properly I think you need to maintain the block-cut tree dynamically, but even then, I don't see how to get better than O(n) in the worse case that the deleted node has O(n) neighbors in the block-cut tree.I also wish that the dynamic connectivity problem could be solved using DSU :) Unfortunately the $$$del$$$ operation only solves the very simple problem of deleting an element from a set, not deleting a node from a graph.
It's really about perspective. What OP's operations are doing is treating it as merging sets then deleting an element from a given set, not as linking and cutting. (In other words, deleting and cutting are different things.)
Performing what you are desiring would either require dynamic connectivity or, if we can guarantee certain things about the structure of the graph, a link-cut tree could suffice (originally called a dynamic tree).