当前位置: 首页 > news >正文

P1053 [NOIP 2005 提高组] 篝火晚会

题目描述

佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了“小教官”。在军训结束的那天晚上,佳佳被命令组织同学们进行篝火晚会。一共有 n 个同学,编号从 1 到 n。一开始,同学们按照 1,2,⋯,n 的顺序坐成一圈,而实际上每个人都有两个最希望相邻的同学。如何下命令调整同学的次序,形成新的一个圈,使之符合同学们的意愿,成为摆在佳佳面前的一大难题。

佳佳可向同学们下达命令,每一个命令的形式如下:

(b1​,b2​,...bm−1​,bm​)

这里 m 的值是由佳佳决定的,每次命令 m 的值都可以不同。这个命令的作用是移动编号是 b1​,b2​,⋯,bm​ 的这 m 个同学的位置。要求 b1​ 换到 b2​ 的位置上,b2​ 换到 b3​ 的位置上,……,要求 bm​ 换到 b1​ 的位置上。执行每个命令都需要一些代价。我们假定如果一个命令要移动 m 个人的位置,那么这个命令的代价就是 m。我们需要佳佳用最少的总代价实现同学们的意愿,你能帮助佳佳吗?

输入格式

第一行是一个整数 n,表示一共有 n 个同学。

其后 n 行每行包括 2 个不同的正整数,以一个空格隔开,分别表示编号是 1 的同学最希望相邻的两个同学的编号,编号是 2 的同学最希望相邻的两个同学的编号,……,编号是n的同学最希望相邻的两个同学的编号。

输出格式

一个整数,为最小的总代价。如果无论怎么调整都不能符合每个同学的愿望,则输出 −1。

输入输出样例

输入 #1复制

4
3 4
4 3
1 2
1 2

输出 #1复制

2

说明/提示

  • 对于 30% 的数据,满足 n≤1000;
  • 对于 100% 的数据,满足 3≤n≤50000。

【题目来源】

NOIP 2005 提高组第三题


题目解析

题目描述了一个班级同学围坐在篝火周围的情景。初始时,同学们按1~n的顺序顺时针围坐。现在给出两个目标排列方式,要求通过最少的交换操作(每次可以交换任意两个人的位置)将初始排列转换为其中一个目标排列。我们需要计算这个最小的交换次数。

算法分析

关键思路

这道题的核心在于理解排列变换的最小交换次数计算。对于排列变换问题,最小交换次数等于排列长度减去排列中循环的个数。这是因为:

  1. 每个长度为k的循环需要k-1次交换来解开

  2. 总交换次数 = Σ(每个循环的长度-1) = n - 循环个数

解题步骤

  1. 构建目标排列:读取并存储两个目标排列

  2. 检查特殊情况:判断两个目标排列是否互为逆序

  3. 计算循环个数:对于每个目标排列,计算初始排列变换到该排列的循环个数

  4. 求最小交换次数:根据循环个数计算最小交换次数,取两个目标排列中的较小值

代码实现

cpp

#include <iostream>
#include <cstring>
using namespace std;const int MAXN = 50005; // 最大人数int n; // 总人数
int a[MAXN], b[MAXN]; // 两个目标排列
int pos[MAXN]; // 位置映射数组
bool vis[MAXN]; // 访问标记数组// 读取并构建目标排列
void read(int arr[]) {for (int i = 1; i <= n; ++i) {cin >> arr[i];}
}// 计算到目标排列的最小交换次数
int calc(int target[]) {memset(vis, false, sizeof(vis)); // 初始化访问标记memset(pos, 0, sizeof(pos)); // 初始化位置映射// 建立目标排列的位置映射for (int i = 1; i <= n; ++i) {pos[target[i]] = i;}int cycle = 0; // 循环计数器for (int i = 1; i <= n; ++i) {if (!vis[i]) {cycle++; // 发现新循环int j = i;// 标记整个循环while (!vis[j]) {vis[j] = true;j = pos[j];}}}return n - cycle; // 最小交换次数
}int main() {cin >> n;read(a); // 读取第一个目标排列read(b); // 读取第二个目标排列// 检查两个排列是否互为逆序bool is_rev = true;for (int i = 1; i <= n; ++i) {if (a[i] != b[n - i + 1]) {is_rev = false;break;}}// 计算两个目标排列的最小交换次数int ans1 = calc(a);int ans2 = calc(b);// 输出较小值cout << min(ans1, ans2) << endl;return 0;
}

代码解析

数据结构

  1. a[MAXN] 和 b[MAXN]:存储两个目标排列

  2. pos[MAXN]:建立从同学编号到其在目标排列中位置的映射

  3. vis[MAXN]:标记同学是否已被访问,用于循环检测

核心函数

  1. read()函数:

    • 功能:读取并存储目标排列

    • 参数:目标排列数组

    • 实现:简单循环读取

  2. calc()函数:

    • 功能:计算到目标排列的最小交换次数

    • 参数:目标排列数组

    • 实现步骤:
      a. 初始化访问标记和位置映射
      b. 建立目标排列的位置映射
      c. 检测循环个数
      d. 返回n-cycle作为最小交换次数

主函数逻辑

  1. 读取输入数据

  2. 检查特殊排列情况(逆序)

  3. 分别计算两个目标排列的最小交换次数

  4. 输出较小值

复杂度分析

  • 时间复杂度:O(n)

    • 读取输入:O(n)

    • 逆序检查:O(n)

    • 循环检测:O(n)

    • 总体线性复杂度

  • 空间复杂度:O(n)

    • 使用了多个大小为n的数组

    • 空间使用与输入规模成正比

优化与注意事项

  1. 逆序检查优化

    • 两个互为逆序的排列具有相同的最小交换次数

    • 检测到这种情况可以节省一次calc计算

  2. 边界条件处理

    • n=1时直接返回0

    • 确保数组访问不越界

  3. 循环检测技巧

    • 使用vis数组避免重复计算

    • 通过位置映射快速找到下一个元素

数学原理深入

排列与循环分解

任何排列都可以分解为若干不相交的循环。例如:
排列(2,4,1,3)可以分解为两个循环:(1→2→4→3→1)和(5→5)

交换次数计算

将一个排列转换为另一个排列的最小交换次数等于n-c,其中:

  • n是排列长度

  • c是循环个数

这是因为:

  • 恒等排列有n个循环(每个元素自成一个循环)

  • 每次交换最多能将循环个数增加1

  • 因此需要至少n-c次交换

实例分析

假设n=4,初始排列为(1,2,3,4)

目标排列1:(2,1,4,3)

  • 循环分解:(1→2→1), (3→4→3)

  • 循环个数:2

  • 最小交换次数:4-2=2

目标排列2:(4,3,2,1)

  • 循环分解:(1→4→1), (2→3→2)

  • 循环个数:2

  • 最小交换次数:4-2=2

总结

本题通过排列的循环分解,巧妙地计算了最小交换次数。关键在于:

  1. 理解排列的循环结构

  2. 掌握位置映射的建立方法

  3. 实现高效的循环检测算法

这种基于循环分解的方法不仅适用于本题,也是解决许多排列相关问题的通用思路。

http://www.dtcms.com/a/323608.html

相关文章:

  • 【C/C++】详解内存对齐问题,C语言内存对齐整理
  • vulhub-Beelzebub靶机
  • 计算网络相关知识
  • 第15届蓝桥杯Scratch图形化省赛初级组2024年8月24日真题
  • 模型微调与RAG在问答系统中的对比分析
  • [激光原理与应用-205]:光学器件 - LD与DFB的比较
  • leetcode 11. 盛最多水的容器 -java
  • Kubernetes CronJob bug解决
  • B站小波变换视频笔记
  • 原创邮件合并Python工具使用说明(附源码)
  • python---变量作用域
  • 零拷贝技术:提升传统I/O的性能
  • 【C++】string 的特性和使用
  • 欢迎走进《励曼旋耕》
  • LintCode第547题-两数组的交集
  • leetcode 49. 字母异位词分组 - java
  • [激光原理与应用-202]:光学器件 - 增益晶体 - Nd:YVO₄增益晶体的制造过程与使用过程
  • vite面试题及详细答案120题(61-90)
  • 简单聊聊PowerShell
  • Effective C++ 条款32:确定你的public继承塑模出 is-a 关系
  • 【读代码】深度解析 SmolAgents Open Deep Research
  • 杰理-AW-断言-log
  • 计算机网络基础(三)
  • [Shell编程] Shell 函数
  • PyQt5技术栈简述
  • .htaccess 文件上传漏洞绕过总结
  • Linux文件操作详解:一切皆文件
  • 编辑距离-二维动态规划
  • SkyWalking-3--Java Agent开发和集成示例
  • AI智能编程工具汇总