[题解] CF1131F Asya And Kittens
前言
题意
有一个长为 \(n\) 的排列,一开始每个数都是一个独立的联通块。有 \(n-1\) 次操作,每次要求 \(x_i\) 和 \(y_i\) 所在的联通块相邻,然后把这两个联通块合并。求一个合法的排列使得所有操作合法。保证有解。
思路
先说这道题的坑点,合并时 \(x_i\) 必须放在左边,合并时 \(y_i\) 必须放在右边。
首先将每一个块看成一个独立的连通块。对于每一个连通块,可以看做是一条链。
于是可以使用链来维护,手写。重要的是链首和链尾。
显然初始化时,应该把每一个链的链首与链尾初始化成自己。
而对于查找每一个点属于哪一个连通块可以使用并查集进行维护。
并查集合并时,将左边的链连接至右边的链的头部,让后将左边的链尾设置尾右边的链尾。
由于右连通块的父亲将置为左连通块,所以右连通块的信息已经没有用处了,就不需要更新。
最后从整条链的链首开始,一直输出到链尾。
Code
时间复杂度尾 \(O(α(n)n)\) 。
#include <cstdio>
namespace Quick_Function {
template <typename Temp> void Read(Temp &x) {
x = 0; char ch = getchar(); bool op = 0;
while(ch < '0' || ch > '9') { if(ch == '-') op = 1; ch = getchar(); }
while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
if(op) x = -x;
}
template <typename T, typename... Args> void Read(T &t, Args &... args) { Read(t); Read(args...); }
template <typename Temp> Temp Max(Temp x, Temp y) { return x > y ? x : y; }
template <typename Temp> Temp Min(Temp x, Temp y) { return x < y ? x : y; }
template <typename Temp> Temp Abs(Temp x) { return x < 0 ? (-x) : x; }
template <typename Temp> void Swap(Temp &x, Temp &y) { x ^= y ^= x ^= y; }
}
using namespace Quick_Function;
const int MAXN = 15e4 + 5;
int fa[MAXN], head[MAXN], tail[MAXN];
int nxt[MAXN], a[MAXN], n;
struct DSU {
int Find(int x) { return fa[x] == x ? x : (fa[x] = Find(fa[x])); }
void Union(int x, int y) {
int fax = Find(x), fay = Find(y);
if(fax == fay) return;
fa[fay] = fax;
nxt[tail[fax]] = head[fay];
tail[fax] = tail[fay];
}
void Init() { for(int i = 1; i <= n; i++) fa[i] = head[i] = tail[i] = i, nxt[i] = 0; }
void Print(int x) {
if(!x) return;
printf("%d ", x);
Print(nxt[x]);
}
};
DSU dsu;
int main() {
Read(n); dsu.Init();
for(int i = 1, x, y; i < n; i++) Read(x, y), dsu.Union(x, y);
dsu.Print(head[dsu.Find(1)]);
return 0;
}