【2025年莆田市第五期C++专项第三轮选拔真题(初中组)】魔法石

【2025年莆田市第五期C++专项第三轮选拔真题(初中组)】魔法石

题目背景

在遥远的“数域王国”,有一个特殊的试炼场——交换广场。这里有一个传奇的试炼,称为“数列排序挑战”。试炼的主角是一名年轻的勇士,名叫阿尔法。阿尔法得到了一个任务:他需要将一组神秘的数字石从小到大排序。每个数字石上刻着不同的数字,且这些数字之间没有任何重复。

每当阿尔法站在广场的中央时,他会发现这些数字石被随机放置在广场的不同位置,而阿尔法手中的魔法杖赋予了他一种特殊能力——交换魔法。这个魔法允许他每次交换任意两颗数字石的位置,但每次交换后,他只能得到一个非常小的提示:“当前石头序列离完全排序还有几步之遥。”

阿尔法决定尽可能高效地完成这个任务,希望尽量少地使用交换魔法,以节省时间和体力。那么问题来了:阿尔法最少需要多少次交换,才能让这些数字石按从小到大的顺序排列呢?

简单来说定一个数列 a a a,这个数列满足 a i ≠ a j a_i \not =a_j ai=aj i ≠ j i\not=j i=j​),现在要求你把这个数列从小到大排序,每次允许你交换其中任意一对数,请问最少需要几次交换?

输入格式

第一行是一个整数,代表数字个数 n n n

第二行有 n n n 个整数用空格分隔开,表示数列 a a a

输出格式

只有一行,包含一个数,表示最少的交换次数。

输入输出样例 #1

输入 #1

8
8 23 4 16 77 1 53 100

输出 #1

5

输入 #2

5
1 4 3 2 5

输出 #3

1

说明/提示

数据规模与约定

对于20%的数据, 1 ≤ n ≤ 2 1 \le n \le 2 1n2 , 1 < a i < 2 1\lt a_i\lt2 1<ai<2;

对于另 40 % 40\% 40% 的数据,保证 1 ≤ n ≤ 1000 1\le n\le1000 1n1000 1 < a i < 1000 1\lt a_i\lt1000 1<ai<1000, a i ≤ a i − 1 a_i \le a_{i-1} aiai1;

对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 10 5 1\le n\le10^5 1n105 1 < a i < 10 5 1\lt a_i\lt10^{5} 1<ai<105

问题重述

给定一个不包含重复元素的数列,每次可以交换任意两个元素的位置,问最少需要多少次交换才能将数列排序为升序。

解题思路

这个问题可以转化为计算数列中的环的数量。每个环至少需要交换(环的大小-1)次才能归位。因此,最少交换次数等于所有环的大小减一的总和。

具体步骤:

  1. 建立映射关系:首先对原数组进行排序,然后记录每个元素在排序后数组中的位置。
  2. 寻找环:遍历原始数组,通过映射关系找到每个元素应该在的位置,从而发现环。
  3. 计算交换次数:对于每个大小为k的环,需要k-1次交换来归位所有元素。

代码实现

#include <bits/stdc++.h>
using namespace std;

struct Element {
    int value;  // 元素的值
    int index;  // 元素在原数组中的位置
};

int main() {
    ios::sync_with_stdio(false);  // 加速输入输出
    cin.tie(nullptr);             // 解除cin与cout的绑定
    
    int n;
    cin >> n;
    
    vector<Element> elements(n + 1);  // 1-based索引
    vector<int> sortedPosition(n + 1); // 记录排序后每个元素的位置
    
    // 读取输入并记录原始位置
    for (int i = 1; i <= n; ++i) {
        cin >> elements[i].value;
        elements[i].index = i;
    }
    
    // 按值排序
    sort(elements.begin() + 1, elements.end(), 
        [](const Element& a, const Element& b) {
            return a.value < b.value;
        });
    
    // 建立排序后位置到原始位置的映射
    for (int i = 1; i <= n; ++i) {
        sortedPosition[elements[i].index] = i;
    }
    
    int swapCount = 0;
    vector<bool> visited(n + 1, false); // 标记是否访问过
    
    // 寻找环并计算交换次数
    for (int i = 1; i <= n; ++i) {
        if (!visited[i]) {
            int j = i;
            int cycleSize = 0;
            
            while (!visited[j]) {
                visited[j] = true;
                j = sortedPosition[j];
                cycleSize++;
            }
            
            if (cycleSize > 1) {
                swapCount += (cycleSize - 1);
            }
        }
    }
    
    cout << swapCount << endl;
    
    return 0;
}

代码解释

  1. 输入处理:使用快速输入方法读取数据,并记录每个元素的原始位置。
  2. 排序:对元素按值进行排序,得到排序后的位置映射。
  3. 环检测:通过遍历原始位置数组,检测环的存在。对于每个未访问的元素,追踪其应该在的位置,直到回到起点,形成一个环。
  4. 计算交换次数:每个环需要(环大小-1)次交换来归位所有元素。

复杂度分析

时间复杂度:O(n log n)(排序的时间复杂度)
空间复杂度:O(n)(存储元素和位置映射)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会叫的恐龙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值