题解
题目大意,1到n编号的数字最开始每个都属于一个单独的集合,告诉你相邻的两个集合并且合并在一起,最终会合并为同一个集合,输出任意满足条件的原序列。
并查集解法:
使用两个并查集vl, vr分别记录当前集合的最左元素和最右元素,在合并时将记录最左元素的根节点接在左侧集合的根节点下,记录右侧的接在右侧根节点下即vr[ar] = br, vl[bl] = al,ar, br为两个集合的根节点。
在拼接过程中再记录拼接点的左右元素分别是谁,即r[ar] = bl, l[bl] = ar。
最后找到最左节点通过r数组遍历输出即可。
启发式合并解法:
使用并查集v数组记录每个集合的根节点,vector数组a按顺序记录每个根节点所包含的元素。
合并集合时将小的集合接在大的集合下(保证nlogn),并将vector合并进大的集合。
AC代码
并查集:
#include <stdio.h>
#include <bits/stdc++.h>
#define fst first
#define sed second
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 10;
int vl[N], vr[N]; //集合最左最右节点 以最左最右节点为根
int l[N], r[N]; //节点左侧右侧节点
inline int find(int *v, int x)
{
return v[x] == x ? x : v[x] = find(v, v[x]);
}
int main()
{
#ifdef LOCAL
freopen("C:/input.txt", "r", stdin);
#endif
int n;
cin >> n;
for (int i = 1; i <= n; i++)
vl[i] = vr[i] = l[i] = r[i] = i;
for (int i = 1; i < n; i++)
{
int a, b;
scanf("%d%d", &a, &b); //aaaabbbb
int al = find(vl, a), ar = find(vr, a);
int bl = find(vl, b), br = find(vr, b);
r[ar] = bl, l[bl] = ar; //为拼接点记录左右节点
vr[ar] = br, vl[bl] = al; //合并集合
}
int k = find(l, 1); //最左节点
for (int j = 1; j <= n; j++)
{
printf("%d ", k);
k = r[k];
}
cout << endl;
return 0;
}
启发式合并:
#include <stdio.h>
#include <bits/stdc++.h>
#define fst first
#define sed second
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 10;
vector<int> a[N];
int v[N];
int find(int x)
{
return v[x] == x ? x : v[x] = find(v[x]);
}
void join(int x, int y)
{
x = find(x), y = find(y); //得到根节点
if (a[x].size() > a[y].size())
swap(x, y);
v[x] = y; //将小的接在大的下面
a[y].insert(a[y].end(), a[x].begin(), a[x].end()); //将元素接在y的右边
a[x].clear();
}
int main()
{
#ifdef LOCAL
freopen("C:/input.txt", "r", stdin);
#endif
int n;
cin >> n;
for (int i = 1; i <= n; i++)
v[i] = i, a[i].push_back(i);
for (int i = 1; i < n; i++)
{
int u, v;
scanf("%d%d", &u, &v);
join(u, v);
}
int r = find(1); //只剩一个集合
for (int x : a[r])
printf("%d ", x);
cout << endl;
return 0;
}