![算法训练营:海量图解+竞赛刷题(进阶篇)](https://wfqqreader-1252317822.image.myqcloud.com/cover/239/47379239/b_47379239.jpg)
训练2 方块栈
题目描述(POJ1988):贝西正在玩方块游戏,方块编号为1~N(1≤N≤30,000),开始时每个方块都相当于一个栈。贝西执行P个(1≤P≤100,000)操作,操作类型有两种:M X Y,将包含X的栈整体移动到包含Y的栈顶部;C X,查询X方块下的方块数量。请统计贝西每个操作的结果。
输入:第1行为单个整数P,表示操作的数量。第2~P+1行:每一行都描述一个操作(注意:N的值不会出现在输入文件中,没有一种移动操作会请求将栈移动到自身)。
输出:对每个C操作,都输出统计结果。
![](https://epubservercos.yuewen.com/B221B0/26763902601483406/epubprivate/OEBPS/Images/40886-00-20-1.jpg?sign=1739244952-A1iY9hMCKlduu6JwO3zB0iC5xUyRh5x6-0-f5c859dde342b99b350b3fb1ecbd71ff)
题解:本题包括移动和计数两种操作,方块的整体移动可以使用二维数组实现,但是操作数量很大,若一个一个地移动,则会超时。整体移动相当于集合的合并,因此可以借助并查集实现,在集合查找和合并时,更新树根下方的方块数量即可。使用并查集可以快速、高效地解决该问题。
1. 算法设计
(1)初始化。初始化每个方块的集合号都为其自身。
(2)查询或者合并。
• C X:查询X的集合号,并输出X方块下的方块数量。d[i]表示第i个方块下的方块数量。查询X的祖宗,在返回过程中将经过路径上节点的集合号统一为祖宗的集合号,将当前节点的d值加上其父节点的d值。
• M X Y:合并X、Y集合号。cnt[i]表示第i个栈的方块数量。首先找到X、Y所在的集合祖宗a、b,然后将a的集合号修改为b,fa[a]=b,更新d[a]=cnt[b],cnt[b]+=cnt[a]。
2. 完美图解
(1)初始化。根据输入样例,初始时每个方块的集合号都为其自身,fa[i]=i;在每个方块下都有0个方块,d[i]=0;每个栈只有1个方块,cnt[i]=1。
(2)合并。M 1 6:将包含1的栈整体移动到包含6的栈。首先找到1和6的祖宗1、6,然后将1的集合号修改为6,fa[1]=6,更新d[1]=cnt[6]=1,cnt[6]+=cnt[1]=2。
![](https://epubservercos.yuewen.com/B221B0/26763902601483406/epubprivate/OEBPS/Images/40886-00-20-2.jpg?sign=1739244952-o90mn97aWRnXly3f9ILEf6v9k2d4ohTM-0-c387f15c39499d06cb102f08075abeb7)
(3)查询。C 1:查询1下面有多少个方块。首先查询1的集合号,找祖宗,在查询过程中将当前节点的d值加上其父节点的d值,d[1]+=d[6]=1。
(4)合并。M 2 4:将包含2的栈整体移动到包含4的栈。首先找到2和4的祖宗2、4,然后将2的集合号修改为4,fa[2]=4,更新d[2]=cnt[4]=1,cnt[4]+=cnt[2]=2。
![](https://epubservercos.yuewen.com/B221B0/26763902601483406/epubprivate/OEBPS/Images/40886-00-21-1.jpg?sign=1739244952-NxGTCrNCGaPOMv2m6PhBXaVqMsmLlWxG-0-2748534cc0adff476631d359920a4fff)
(5)合并。M 2 6:将包含2的栈整体移动到包含6的栈。首先找到2和6的祖宗4、6,然后将4的集合号修改为6,fa[4]=6,更新d[4]=cnt[6]=2,cnt[6]+=cnt[4]=4(注意:只修改祖宗集合号,2号方块的集合号及d值并没有修改,下次查询2的集合号时才会更新。这正是并查集的妙处)。
![](https://epubservercos.yuewen.com/B221B0/26763902601483406/epubprivate/OEBPS/Images/40886-00-21-2.jpg?sign=1739244952-70RomQTO1KXwrlzC59DgUhYo5BBap00C-0-d615e8a18ebb2ae3eebac0528b6b9d53)
(6)查询。C 3:查询3下面有多少个方块。首先查询到3的集合号为3,d[3]=0。
(7)查询。C 4:查询4下面有多少个方块。首先查询4的集合号,在查询过程中将当前节点的集合号修改为其父节点的集合号,将当前节点的d值加上其父节点的d值,d[4]+=d[6]=2。
(8)若继续查询C 2,则查询2下面有多少个方块。先查询2的祖宗,在返回过程中将当前节点的d值加上其父节点的d值,d[2]+=d[4]=3。
![](https://epubservercos.yuewen.com/B221B0/26763902601483406/epubprivate/OEBPS/Images/40886-00-21-3.jpg?sign=1739244952-WHgWAeEXflMTe5B74eeZDDs8huwQDZwt-0-919989ced94acc3e18bf8663725d7265)
3. 算法实现
(1)初始化。
![](https://epubservercos.yuewen.com/B221B0/26763902601483406/epubprivate/OEBPS/Images/40886-00-21-4.jpg?sign=1739244952-dwf3l9tJDJjBvxrlZJhbLEQia9Xu10zZ-0-53717a72707d5127d51e8eb646363812)
(2)查询。查询x的集合号,集合号等于自身时停止。在返回过程中,将经过路径上节点的集合号都统一为祖宗的集合号,将当前节点的d值加上其父节点的d值。
![](https://epubservercos.yuewen.com/B221B0/26763902601483406/epubprivate/OEBPS/Images/40886-00-22-1.jpg?sign=1739244952-t9BKfUYCSw5IJfORfcy1UBVSyS6DUc6H-0-8dd88edcba480968a74579ef1bea3fe5)
(3)合并。首先找到x、y所在的集合祖宗a、b,然后将a的集合号修改为b,更新d[a]=cnt[b],cnt[b]+=cnt[a]。
![](https://epubservercos.yuewen.com/B221B0/26763902601483406/epubprivate/OEBPS/Images/40886-00-22-2.jpg?sign=1739244952-C8s2uScBSualKV3uMjwCzqy2DRiAFcjR-0-edc4fc052f8ff3add99a6b8509f4e9b4)