【题解·C/C++】并查集的灵活使用题目,洛谷P1196 [NOI2002] 银河英雄传说

文章介绍了如何使用并查集和延迟更新策略来处理Mij和Cij两种指令,其中Mij用于合并序列,Cij用于查询节点间元素个数。通过维护Index、Fa和Nums三个数组,实现了对序列动态修改和高效查询的功能。
摘要由CSDN通过智能技术生成

题目简介

给出两种指令:M和C。
M i j:将 i i i 插入到 j j j 的末尾,按照顺序将整个序列插入。
C i j:查询 i i i j j j 节点之间的元素个数。
洛谷P1196跳转链接

解题思路

首先注意到,如果有节点p,而它的子节点l1,l2,l3的father都是p。
那么我们只需要修改p,l1,l2,l3的值就可以顺便修改了(类似LazyTag延迟更新),当查询到l1,l2,l3头上的时候,再去更新。

现在设计三个数组:Index、Fa、Nums数组。

第一个Index数组用于保存此节点全局的编号。第二个保存这个节点的爸爸。而第三个保存的是此节点往后的所有元素个数。

比如序列【(1)、(2)、(3)】节点的Index是1,2,3

而序列【(4)、(5)】节点Index是1,2

那么现在想要【(4)、(5)、(1)、(2)、(3)】的话,正确的节点Index应该是,1,2,3,4,5但是现在却是1,2,1,2,3。

好在,我们的(2)、(3)指向(1)因为(1)是他们爸爸。而(5)指向(4)。并查集合并的时候使用(1)指向(4)然后因为知道了【(1),(2),(3)】元素的个数,所以可以修改Nums[(4)]=2(之前是两个元素)+3(又加入3个元素);

然后可以修改全局索引Index[(1)]=3,因为我们知道之前的Nums[(4)]有两个。所以说,现在是1,2,3,2,3。

那么后面两个2,3怎么更正呢?通过查询自己父节点Index[(1)]=3,可以知道如果它之前没有链接其他序列,它应该是Index[(1)]=1,所以我们知道偏移量是2。因为头部多了两个元素,所以,后面的肯定也是两个元素。在Query的过程中,就可以轻松更正Index[(2)]=2+2=4查询(3)的时候也可以轻松更正Index[(3)]=3+2=5.

所以最后:【(4)、(5)、(1)、(2)、(3)】的整个全局的Index是:1,2,3,4,5。啦!

现在,你已经知道了,整个序列元素是5个·,也知道了Index的编号,那么Nums还不好更新吗?So Easy了~找找关系就好啦!

(っ °Д °;)っ

AC代码

// VsCode C++模板 | (●'◡'●)
#include <bits/stdc++.h>

#include <iostream>
using namespace std;
typedef long long LL;
#define MaxN 30000 + 5
int T, Index[MaxN], Fa[MaxN], Nums[MaxN];
int QueryRoot(int node) {
    if (Fa[node] == node) { return node; }
    auto root = QueryRoot(Fa[node]);

    int& node_root = Fa[node]; // now root
    int _move = Index[node_root] - 1;
    Index[node] += _move;
    Nums[node] = Nums[root] - Index[node] + 1;
    node_root = root;

    return root;
}
void Mergen(int i, int j) {
    int rootI = QueryRoot(i);
    int rootJ = QueryRoot(j);
    Index[rootI] += Nums[rootJ];
    Nums[rootJ] += Nums[rootI];
    Fa[rootI] = rootJ;
}
int main() {
    for (int i = 1;i <= MaxN - 5;i++) {
        Index[i] = 1;
        Nums[i] = 1;
        Fa[i] = i;
    }
    scanf("%d", &T);
    while (T--) {
        char opt; int i, j;
        cin >> opt >> i >> j;
        if (opt == 'M') { //Merg
            Mergen(i, j);
        } else { //Query
            int rootI = QueryRoot(i), rootJ = QueryRoot(j);
            if (rootI != rootJ) printf("-1\n"); else {
                printf("%d\n", abs(Index[i] - Index[j]) - 1);
            }
        }
    }


    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值