问题描述:
cube stacking
N个方块,编号为1-N,现在有P个操作,分为两种:1 M x y表示有x的那一堆放到有y的那一堆上面
2 C x表示询问在x下方有多少个方块
N<=30000,p<=10^5
第一行输入操作次数P。
输入样例:
6
M 1 6
C 1
M 2 4
M 2 6
C 3
C 4
输出样例:
1
0
2
并查集大意就是开一个数组,储存每个元素的前驱,这在dijkstra算法中似乎用到过。
来看这道题目
知道并查集之后把这个题目画了画图,ok,so easy,不就是改几个前驱嘛…然后带着电脑带了一堆作业到图书馆,然后…作业一点没动
如图,画个图就好理解了
如果要严格的模拟这个过程,move时,并查集的底端就要接到另一个盒子集合的顶端,但是我们定义的并查集只能往下搜索,想到的解决方法如下:
1 开一个top数组,存放当前所在集合的top元素。这意味着遍历、遍历、遍历…越到后面越变态,时间可以爆炸了
2 往上搜索,每次都要遍历,一遍一遍对自己说:“不能遍历,不能把数模的习惯带过来…”
无数次阻止自己遍历的欲望之后,我们只好接受并查集合并了,就是只能接到另一个集合的根部。
这里我的解决办法是定义一个skip修正这种接法对真实情况的歪曲,每次的合并目标如果本身大小大于一,就要改变并上去的集合根部的skip,每次询问只要上溯到根部,再加上每次的skip就好了。
本题盒子集合不可以拆开(也就是永远是越堆越大),所以这么做是可行的,不过如果可以随便拆的话,网上书上的解法也得失效,就题论题。
#include<iostream>
using namespace std;
int cube[30000];//main中先把它初始化为根
int skip[30000];//连接跳跃
int at[30000];//根部所在集合的盒子数
int find(int x)//注意编号从1开始
{
while (cube[x - 1] != x)
{
x = cube[x - 1];
}
return x;
}
void move(int x, int y)//每次都直接接在根上,然后相加
{
int stackbottom = find(x);
//合并
int target = find(y);
cube[stackbottom - 1] = target;
skip[stackbottom - 1] = at[target-1]-1;//跳跃数
at[target - 1] += at[stackbottom - 1];
at[stackbottom - 1] = 1;//记为0更具有统一性,但对于编程不方便
}
int ques(int x)
{
int count=0;
while (cube[x - 1] != x)
{
count++;
count += skip[x - 1];
x = cube[x - 1];
}
return count;
}
int main()
{
//初始化
for (int i = 0; i < 30000; i++)
{
cube[i] = i + 1;
skip[i] = 0;
at[i] = 1;
}
int P;//操作数
char code;//操作类型
cin >> P;
while (P--)
{
cin >> code;
if (code == 'M')
{
int x, y;
cin >> x >> y;
move(x, y);
}
if (code == 'C')
{
int z;
cin >> z;
cout << ques(z) << endl;
}
}
return 0;
}