并查集+树高【P1196 [NOI2002] 银河英雄传说】

P1196 [NOI2002] 银河英雄传说

题目背景

公元 580158015801 年,地球居民迁至金牛座 α\alphaα 第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展。

宇宙历 799799799 年,银河系的两大军事集团在巴米利恩星域爆发战争。泰山压顶集团派宇宙舰队司令莱因哈特率领十万余艘战舰出征,气吞山河集团点名将杨威利组织麾下三万艘战舰迎敌。

题目描述

杨威利擅长排兵布阵,巧妙运用各种战术屡次以少胜多,难免恣生骄气。在这次决战中,他将巴米利恩星域战场划分成 300003000030000 列,每列依次编号为 1,2,...,300001, 2,\ldots ,300001,2,...,30000。之后,他把自己的战舰也依次编号为 1,2,...,300001, 2, \ldots , 300001,2,...,30000,让第 iii 号战舰处于第 iii 列,形成"一字长蛇阵",诱敌深入。这是初始阵形。当进犯之敌到达时,杨威利会多次发布合并指令,将大部分战舰集中在某几列上,实施密集攻击。合并指令为 M i j,含义为第 iii 号战舰所在的整个战舰队列,作为一个整体(头在前尾在后)接至第 jjj 号战舰所在的战舰队列的尾部。显然战舰队列是由处于同一列的一个或多个战舰组成的。合并指令的执行结果会使队列增大。

然而,老谋深算的莱因哈特早已在战略上取得了主动。在交战中,他可以通过庞大的情报网络随时监听杨威利的舰队调动指令。

在杨威利发布指令调动舰队的同时,莱因哈特为了及时了解当前杨威利的战舰分布情况,也会发出一些询问指令:C i j。该指令意思是,询问电脑,杨威利的第 iii 号战舰与第 jjj 号战舰当前是否在同一列中,如果在同一列中,那么它们之间布置有多少战舰。

作为一个资深的高级程序设计员,你被要求编写程序分析杨威利的指令,以及回答莱因哈特的询问。

最终的决战已经展开,银河的历史又翻过了一页......

输入格式

第一行有一个整数 TTT(1≤T≤5×1051 \le T \le 5 \times 10^51≤T≤5×105),表示总共有 TTT 条指令。

以下有 TTT 行,每行有一条指令。指令有两种格式:

  1. M i j:iii 和 jjj 是两个整数(1≤i,j≤300001 \le i,j \le 300001≤i,j≤30000),表示指令涉及的战舰编号。该指令是莱因哈特窃听到的杨威利发布的舰队调动指令,并且保证第 iii 号战舰与第 jjj 号战舰不在同一列。

  2. C i j:iii 和 jjj 是两个整数(1≤i,j≤300001 \le i,j \le 300001≤i,j≤30000),表示指令涉及的战舰编号。该指令是莱因哈特发布的询问指令。

每条指令中都保证 i≠ji\neq ji=j。

输出格式

依次对输入的每一条指令进行分析和处理:

  • 如果是杨威利发布的舰队调动指令,则表示舰队排列发生了变化,你的程序要注意到这一点,但是不要输出任何信息。
  • 如果是莱因哈特发布的询问指令,你的程序要输出一行,仅包含一个整数,表示在同一列上,第 iii 号战舰与第 jjj 号战舰之间布置的战舰数目。如果第 iii 号战舰与第 jjj 号战舰当前不在同一列上,则输出 −1-1−1。

输入输出样例 #1

输入 #1

复制代码
4
M 2 3
C 1 2
M 2 4
C 4 2

输出 #1

复制代码
-1
1

说明/提示

样例解释

战舰位置图:表格中阿拉伯数字表示战舰编号。

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N=3e4+5;
/*
压缩路径的并查集本质是树形结构,路径压缩的递归过程堪称精妙:
查询根节点的同时,回溯更新路径上所有父辈节点的根为最终根,
========== 核心关键 ==========
无论题意描述多复杂,我们**完全不需要遍历任何队列/集合元素**,
不需要显性维护队列结构、不需要合并队列元素、不需要遍历根的子节点,
仅靠三个核心参数就能精准维系战舰间的所有关联关系:
f[x]:x的父节点(递归更新后最终指向根)
h[x]:x到根节点的路径上的元素总数(路径长度)
amount[x]:以x为根的集合的元素总数(集合大小)
这三个参数足以解决所有核心问题,多余的遍历操作完全是画蛇添足!
*/
struct node{// 并查集核心节点结构,仅需三个参数,无需任何遍历操作
    int f,     // 父节点(递归更新后为根节点)
        h,     // 当前节点到根节点的路径元素总数(路径长度)
        amount;// 以当前节点为根的集合的元素总数(集合大小)
}f[N];

// 查询x的根节点,核心递归过程:仅更新参数,完全不需要遍历
// 不仅找根,更回溯更新路径上所有节点的父节点和路径长度
int fa(int x){
    if(x!=f[x].f){// 若自身非根,触发递归更新逻辑
        int oldf=f[x].f;// 暂存更新前的父节点(仅用于路径长度计算,无需遍历)
        f[x].f=fa(f[x].f);// 递归查询父节点的根,回溯时更新当前节点的根为最终根
        f[x].h+=f[oldf].h;//把长度列归到别的队列后,在fa就能更新路径长度
    }
    return f[x].f;// 返回最终根节点(递归终点+回溯起点,全程无遍历)
}

// 合并操作:将x所在集合(战舰列)连接到y所在集合(战舰列)
// 核心:仅修改根节点的三个参数,完全不需要遍历任何队列/集合元素
void unite(int x,int y){
    int fx=fa(x),fy=fa(y);// 先查询并更新x、y的根(顺带完成路径压缩,无遍历)
    if(fx==fy)return;// 同属一个集合,无需合并,无任何遍历操作
    f[fx].f=fy;// 将x集合的根挂到y集合的根下,仅改参数,无需遍历合并队列
    f[fx].h=f[fy].amount;// x集合根到新根的路径长度 = y集合总数,无遍历
    f[fy].amount+=f[fx].amount;// 合并集合大小,仅参数累加,无任何遍历
}

int t,x,y;
char op;
int main(){
    // 初始化:每个节点自成集合,仅设置三个核心参数,无遍历
    for(int i=1;i<N;i++)f[i]={i,0,1};
    cin>>t;
    while(t--){
        cin>>op>>x>>y;
        if(op=='M')unite(x,y);// 合并操作:仅参数修改,无遍历
        else{
            int fx=fa(x),fy=fa(y);// 查询根的同时更新参数,无遍历
            if(fx==fy){// 同属一个集合,计算两战舰间的数量
                // 仅靠路径长度参数计算结果,完全不需要遍历战舰队列
                cout<<abs(f[x].h-f[y].h)-1<<endl;
            }
            else cout<<"-1\n";// 不同集合,无关联,无遍历
        }
    }
    return 0;
}

小结

并查集体现了递归的玄妙,这里的递归时刻都在发生,不仅是查询根更要更新根。

把a集合连接到b集合中,用数组还是容器,其实这里根本不需要遍历某个根元素。

核心递归逻辑:fa()函数的递归不仅是查询根节点,更在回溯时动态更新路径上所有节点的父节点和路径长度,这是并查集路径压缩的精髓;

极简参数设计:仅用f(根)、h(路径长)、amount(集合大小)三个参数维护集合关系,无需遍历操作,体现了并查集的精炼性;就能完成题目所有要求。

合并与查询逻辑:合并时仅修改根节点的关联参数,查询时通过路径长度差计算节点间距,完全契合战舰问题的核心需求。

相关推荐
程序员-King.13 小时前
day158—回溯—全排列(LeetCode-46)
算法·leetcode·深度优先·回溯·递归
程序员-King.1 天前
day159—动态规划—打家劫舍(LeetCode-198)
c++·算法·leetcode·深度优先·回溯·递归
AndrewHZ1 天前
【Python与生活】怎么用python画出好看的分形图?
开发语言·python·生活·可视化·递归·分形
老鼠只爱大米2 天前
LeetCode经典算法面试题 #234:回文链表(双指针法、栈辅助法等多种方法详细解析)
算法·leetcode·链表·递归·双指针·快慢指针·回文链表
程序员-King.3 天前
day152—回溯—电话号码的字母组合(LeetCode-17)
算法·leetcode·深度优先·递归
程序员-King.3 天前
day153—回溯—子集(LeetCode-78)
算法·leetcode·回溯·递归
程序员-King.4 天前
day143—递归—对称二叉树(LeetCode-101)
数据结构·算法·leetcode·二叉树·递归
程序员-King.4 天前
day144—递归—平衡二叉树(LeetCode-110)
算法·leetcode·二叉树·递归
老鼠只爱大米4 天前
LeetCode经典算法面试题 #394:字符串解码(递归、双栈、迭代构建等五种实现方案详解)
算法·leetcode·面试·递归··字符串解码