https://acm.sjtu.edu.cn/OnlineJudge/problem/1118
由于题目含有大量的合并和查询操作,很容易想到用并查集来做。关键在于,如何在合并集合的同时维护题目要求的信息。
并查集的特点是,任何操作都可以变化为对集合根的操作。但反过来,却无法把根的状态改变直接反映到下面的每个集合元素中。在本题中,具体体现就是一堆旅行团会被根带着一起旅行,但无法在近似
O(1)
O
(
1
)
的时间修改每个旅行团已经走过的城市个数。因此,这里采用了类似于线段树中lazy标记的思想,先记录根结点带着下面的旅行团走过了几个城市,合并时再下推标记,把
O(n)
O
(
n
)
的下推操作的次数尽可能减少。
具体如下:
void merge(int x, int y){
int p = find(x), q = find(y);
sum[q] += sum[p]; //sum[k]记录k号原始旅行团所在的大旅行团包含多少原始旅行团
for (int i = 1; i <= n; ++i){ //下推标记
int t = find(i);
if (t == p || t == q)
trl[i] += ex[t]; //ex[k]记录k号原始旅行团所在的大旅行团在合并前走过了多少城市
}
ex[p] = ex[q] = 0;
parent[p] = q;
}
case 'T':
cin >> x >> y;
p = find(x);
q = find(y); //找到x和y的根
if (p != q){
a = nowat[p]; //nowat[k]记录k号原始旅行团在哪个城市
b = nowat[q];
now[a] = -1; //now[k]记录k号城市的大旅行团根的编号,-1表示没有旅行团在k号城市
nowat[p] = b;
++ex[p]; //加标记
merge(y, x);
}
break;
case 'Q':
cin >> x;
p = find(x);
cout << nowat[p] << ' ' << sum[p] << ' ' << trl[x] + ex[p] << '\n'; //trl[k]记录k号原始旅行团走过了几个城市
break;