并查集的删除操作
UVA11987--Almost Union-Find
zz:https://blog.csdn.net/scut_pein/article/details/8660719?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
I hope you know the beautiful Union-Find structure. In this problem, you're to implement something similar, but not identical.
The data structure you need to write is also a collection of disjoint sets, supporting 3 operations:
1 p q : Union the sets containing p and q. If p and q are already in the same set, ignore this command.
2 p q : Move p to the set containing q. If p and q are already in the same set, ignore this command
3 p : Return the number of elements and the sum of elements in the set containing p.
Initially, the collection contains n sets: {1}, {2}, {3}, ..., {n}.
Input Description
There are several test cases. Each test case begins with a line containing two integers n and m (1<=n,m<=100,000), the number of integers, and the number of commands. Each of the next m lines contains a command. For every operation, 1<=p,q<=n. The input is terminated by end-of-file (EOF). The size of input file does not exceed 5MB.
Output Description
For each type-3 command, output 2 integers: the number of elements and the sum of elements.
题目大意:有n个集合,提供三种操作
1 p q:将p所在的集合和q所在的集合并起来
2 p q:将p元素移到集合q所在的集合
3 p:求出p所在集合有多少个元素并输出这些元素的和
Sample Input
5 7
1 1 2 //集合1与2合并(集合操作)
2 3 4 //元素3放到元素4所在的集合中
1 3 5 //将[3,4]与[5]合并,得到[3,4,5]
3 4 //查询4所在的集合
2 4 1 //将元素4移动到1所在集合,得到[1,2,4]
3 4
3 3 //查询3所在的集合[3,5]
Sample Output
3 12
3 7
2 8
SOL:
如果进行删除一个点,将其加入到另一个集合时。
会发现如果这个点是开始那个集合中的非叶子点,就很麻烦了。因为“上有老下有小”,“牵一发而动全身”
但如果为叶子点,那就好办多了。
于是对于每个点i,给它“上面”再加一个点N+i出来
这样点i就不会是叶子点了
为了维护这个性质,当点i与点j合并时,我们同时也要保持点J的“叶子”的状态
于是设i的父亲点为N+j就好了。
//假设有5个点,则对1到5这些点,每个点上面还有一个N+i的点
//1上面是6,2上面是7,3上面是8,4上面是9,这样就可以保证我们在删除点的时候
//不会删除根结点了
//如果将1,2,3并在一个集合里面,则(1,2,3,6,7,8)就在一个集合中了
//如果要移动1到4那集合,则先消除它在原集合中的影响
//然后将1的父亲设为9
#include <cstdio>
#include <cstring>
const int N = 200005;
int n, m, parent[N], num[N], sum[N];
int find(int x)
{
return x == parent[x] ? x : parent[x] = find(parent[x]);
}
void init() {
for (int i = 0; i <= n; i++)
{
parent[i] = parent[i + n] = i + n;
sum[i] = sum[i + n] = i;
num[i] = num[i + n] = 1;
}
}
int main()
{
while (~scanf("%d%d", &n, &m))
{
int q, a, b;
init();
while (m--) {
scanf("%d", &q);
if (q == 1) {
scanf("%d%d", &a, &b);
int pa = find(a);
int pb = find(b);
if (pa == pb) continue;
parent[pa] = pb;
num[pb] += num[pa];
sum[pb] += sum[pa];
}
else if (q == 2) {
scanf("%d%d", &a, &b);
int pa = find(a);
int pb = find(b);
if (pa == pb) continue;
parent[a] = pb;
num[pa]--;
num[pb]++;
sum[pa] -= a;
sum[pb] += a;
}
else {
scanf("%d", &a);
int pa = find(a);
printf("%d %d\n", num[pa], sum[pa]);
}
}
}
return 0;
}
Sol:
开始时如最一般的并查集一样开点,对于涉及移动一个元素到另一个集合的操作即第二种操作时.由于不知道这个元素是根结点还是一般的叶子点(叶子点可以直接移过去,所以可以将这个点的在原集合中的影响力清零,然后另新开一个结点将其copy过去,再将新结点并到目标集合中去。
#include <iostream>
#include <cstdio>
using namespace std;
#define maxn 200018
int father[maxn],idx[maxn],num[maxn];
//num来多少个,idx才存
long long int sum[maxn];
int n,m,cnt;
int find(int x)
{
if(x==father[x])return x;
return find(father[x]);
}
void init()
{
for(int i=1;i<=n;i++)
{
father[i]=idx[i]=sum[i]=i;
num[i]=1;
}
cnt=n;
}
void Union(int p,int q)
{
int pp=find(idx[p]),qq=find(idx[q]);
//统一使用idx[i]代表i目前在哪个集合中
father[pp]=qq;
num[qq]+=num[pp];
sum[qq]+=sum[pp];
}
void Delete(int p)
{
int pp=idx[p];
//取出p所在集合的真实的编号,因为存在删除操作,p所在的集合编号是不断变化的
sum[find(pp)]-=p; //消除其影响力
num[find(pp)]--;
idx[p]=++cnt; //新加一个集合出来
sum[idx[p]]=p; //以下如传统操作一样
num[idx[p]]=1;
father[idx[p]]=idx[p];
}
int main()
{
while(scanf("%d%d",&n,&m)==2)
{
init();
int ope,p,q;
for(int i=1;i<=m;i++)
{
scanf("%d",&ope);
if(ope==1)
{
scanf("%d%d",&p,&q);
if(find(idx[p])==find(idx[q]))
continue;
else
Union(p,q);
}
else if(ope==2)
{
scanf("%d%d",&p,&q);
if(find(idx[p])!=find(idx[q]))
{
Delete(p);
Union(p,q);
}
}
else
{
int u;
scanf("%d",&u);
int fuck=find(idx[u]);
printf("%d %lld\n",num[fuck],sum[fuck]);
//这里我用I64d既然WA。。。
}
}
}
return 0;
}
另一个题目:Hdu2473
5 6//5个数字(编号从0开始),6个操作
M 0 1//将0与1所在集合,进行合并
M 1 2//将2与1所在集合,进行合并
M 1 3//将3与1所在集合,进行合并
S 1//将1从所在集合中抽出来
M 1 2
S 3
3 1 //第二组数据了
M 1 2
0 0
Sample Output
Case #1: 3 //[0,1,2],[3],[4]这三个集合
Case #2: 2
Sol:感觉没什么好写的。就是开虚点吧
最开始时每个数字所在集合的编号就是其本身的值
但涉及“踢"操作的时候,就将那个点的集合编号换成另一个没有用过的编号。
#include<iostream>
#include<string.h>
#include<stdlib.h>
#include<algorithm>
#include<set>
using namespace std;
int par[100005];
int vis[100005];//记录节点
int flag;
set<int> S;
void init(int x)
{
for (int i = 0; i<x; i++)
{
vis[i] = i;
}
}
int find(int x)
{
if (par[x] == x)
return x;
else
return par[x]=find(par[x]);
}
void unite(int x, int y)
{
int a = find(x);
int b = find(y);
if (a == b)
{
return ;
}
else
{
par[a] = b;
}
return ;
}
int main()
{
int n,m;
int sase = 0;
while(scanf("%d%d",&n,&m))
{
if(n == 0 && m == 0) break;
sase++;
int cnt = n;
for(int i = 0;i < 100005;i++)
par[i] = i;
for(int i = 0;i < n;i++)
vis[i] = i;
while(m--)
{
char s[3];
int a,b;
scanf("%s",s);
if(s[0] == 'M')
{
scanf("%d%d",&a,&b);
unite(vis[a],vis[b]);
}
else{
scanf("%d",&a);
vis[a] = cnt++;
}
}
S.clear();
for(int i = 0;i < n;i++)
{
S.insert(find(vis[i]));
//对比一般并查集的写法..if(a[i]==i)ans++,可知vis[]数组的作用
}
printf("Case #%d: %d\n",sase,S.size());
}
return 0;
}
————————————————
版权声明:本文为CSDN博主「Hugo5332」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xxxslinyue/article/details/55259798
Sol:
正确的方法是每一个点都设立一个虚拟父亲比如1,2,3的父亲分别是4,5,6,现在合并1,2,3都在一个集合,那他们的父亲都是4,现在删除1,那就给1重新申请一个节点7
现在2,3的父亲是4,1的父亲是7,删除成功。
#include <stdio.h>
#include <algorithm>
#include <math.h>
#include <string.h>
#include <vector>
#include <queue>
#include <map>
#include <stack>
#include <iostream>
#define pi acos(-1.0)
#define INF 0x3f3f3f3f
using namespace std;
#define ll long long
const int maxn=5000010;
int pre[maxn],id,vis[maxn];
int found(int x)
{
if(x!=pre[x])
pre[x]=found(pre[x]);
return pre[x];
}
void Merge(int a,int b)
{
int fx=found(a),fy=found(b);
if(fx!=fy)
pre[fx]=fy;
}
void del(int x)
{
pre[x]=id++;
}
int main()
{
int n,m,Case=0;
while(scanf("%d%d",&n,&m),n+m)
{
for(int i=0;i<=n;i++)
pre[i]=i+n;
for(int i=n;i<=n+n+m;i++)
//本只有N个点,每个点多开一个“上面的”点出来
//另还有M次操作,预先再多开出M个点来
pre[i]=i;
id=n+n;
int a,b; char ch[5];
for(int i=0;i<m;i++)
{
scanf("%s",ch);
if(ch[0]=='M')
{
scanf("%d%d",&a,&b);
Merge(a,b);
}
else
{
scanf("%d",&a);
del(a);
}
}
int ans=0;
memset(vis,0,sizeof vis);
for(int i=0;i<n;i++)
{
int x=found(i);
if(!vis[x])
ans++,vis[x]=1;
}
printf("Case #%d: %d\n",++Case,ans);
}
return 0;
}

浙公网安备 33010602011771号