https://acm.sjtu.edu.cn/OnlineJudge/problem/1056
先看C操作。注意所有操作的参数都是糖果编号,但由于合并操作会将糖果打乱,所以我们需要设法快速找到特定编号的糖果在哪个盒子里。再加上C操作本身的合并,应该用并查集来实现这个功能。
再看Q操作。Q操作询问糖果数量第p多的盒子中有多少糖果,并且指明了1<=p<=10,相对于N是一个很小的量级。因此,考虑采用大根堆来维护当前的糖果盒子。
最后看D操作。同样,这个操作和并查集有关系。我们只需要把该集合最顶端的糖果标记为已吃掉,就可以用并查集的查询操作,在常数时间内查询到该集合内的任一糖果是否还存在。
综上所述,这道题我采用堆+并查集来实现。其中,堆中只存储每个集合最顶端的元素,相当于存储了所有的糖果盒。
核心部分如下:
// Node类型中的size表示该结点所在糖果盒中糖果的数目,但仅当该结点是根节点时才有效;ind表示在a中存储的下标。
// pos数组存储了每个结点在堆中的下标。
// 寻找第k大的函数。由于题目中所给的p(也就是k)非常小,采用删除k-1次堆顶、取得第k大、再把删除的元素重新放回的做法是不会超时的。这也是为何此题不需要更加复杂的数据结构。
int findk(int k){
int tmp = Size;
for (int i = 1; i <= k - 1; ++i){
a[0] = a[1];
a[1] = a[Size];
a[Size] = a[0];
pos[a[1].ind] = 1;
pos[a[Size].ind] = Size;
--Size;
siftdown(1);
}
int ret = a[1].size;
while (Size < tmp){
++Size;
siftup(Size);
}
return ret;
}
//------------
while (m--){
char ch;
int x, y;
cin >> ch;
switch(ch){
case 'C':{
cin >> x >> y;
int px = getParent(x), py = getParent(y);
if (!b[px] || !b[py] || px == py) break; // b存储该糖果盒内的糖果是否存在,用根结点表示
p[px] = py;
a[pos[py]].size += a[pos[px]].size;
del(pos[px]);
siftup(pos[py]);
break;
}
case 'D':{
cin >> x;
int px = getParent(x);
if (!b[px]) break;
del(pos[px]);
b[px] = false;
break;
}
case 'Q':{
cin >> x;
if (Size < x)
cout << "0\n";
else
cout << findk(x) << '\n';
break;
}
}
}