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 ", 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
",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 ",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 ",++Case,ans); } return 0; }