1 条题解

  • 0
    @ 2025-10-16 15:00:10

    问题背景:
    给定一个长度为 nn 的数组,要求通过若干次“交换操作”将其排序。这里的交换操作并不是普通的两两交换,而是一次性选择一组下标,按照某种规则进行配对交换。程序的目标是输出最多 22 轮这样的操作,使得数组有序。

    核心思路:

    1. 排序与置换映射:
    • 输入数组 a[i]a[i],记录其原始下标。
    • 按值排序后,得到每个元素在有序数组中的目标位置。
    • 构建置换映射 k[i]k[i]:表示原始位置 ii 的元素应该去的位置。
    1. 循环分解:
    • 置换可以分解为若干个循环节。
    • 若一个循环节长度大于 11,则需要通过交换操作打破循环。
    • 遍历所有循环节,记录每个循环节中的元素。
    1. 首尾配对交换:
    • 对于循环节 cyccyc,从两端向中间配对:
      • cyc[l]cyc[l]cyc[r]cyc[r] 交换,更新 kk
      • 记录这对下标,分别放入 AABB
    • 这样一轮操作可以同时处理多个循环节。
    1. 构造交换序列:
    • AA 反转后与 BB 拼接,得到本轮的交换序列。
    • 若本轮没有需要交换的元素,说明排序完成。
    • 否则将该序列加入答案 ansans
    1. 轮数限制:
    • 理论上最多只需要 22 轮操作即可完成排序。
    • 程序中有断言 assert(ans.size() <= 2) 保证正确性。
    1. 输出:
    • 输出轮数 ans|ans|
    • 对于每一轮,输出交换的元素个数和具体下标。

    复杂度分析:

    • 排序 O(nlogn)O(n \log n)
    • 循环分解与交换 O(n)O(n)
    • 总体复杂度 O(nlogn)O(n \log n),适合 n3000n \leq 3000
    #include <bits/stdc++.h>
    using namespace std;
    
    typedef vector <int> vi;
    const int N = 3e3 + 5;  // 定义最大数组大小
    
    int n, k[N];           // n: 元素个数, k: 存储置换的映射关系
    bool vis[N];           // 标记数组,用于记录元素是否被访问过
    pair <int, int> a[N];  // 存储(值, 原始索引)的数组
    vector <vi> ans;       // 存储所有交换操作的答案
    
    signed main() {
        ios :: sync_with_stdio(false);
        cin.tie(nullptr);
        cin >> n;
    
        // 读入数据并记录原始位置
        for (int i = 1; i <= n; i++)
            cin >> a[i].first, a[i].second = i;
    
        // 按值排序,得到排序后的位置关系
        sort(a + 1, a + n + 1);
    
        // 构建置换映射:k[i] 表示原始位置i的元素在排序后应该去的位置
        for (int i = 1; i <= n; i++)
            k[a[i].second] = i;
    
        // 通过循环交换操作将数组排序
        while (1) {
            vi A, B;  // A存储前半部分要交换的元素,B存储后半部分
            fill(vis + 1, vis + n + 1, 0);  // 重置访问标记
    
            // 遍历所有元素,寻找循环节
            for (int i = 1; i <= n; i++) {
                if (vis[i])  // 如果已经访问过,跳过
                    continue;
    
                vi cyc;  // 存储当前循环节中的元素
    
                // 遍历整个循环节
                for (int j = i; !vis[j]; j = k[j]) {
                    cyc.push_back(j);  // 将当前元素加入循环节
                    vis[j] = 1;        // 标记为已访问
                }
    
                // 在循环节内部进行交换:首尾配对交换
                for (int l = 0, r = (int)cyc.size() - 1; l < r; l++, r--) {
                    swap(k[cyc[l]], k[cyc[r]]);  // 交换映射关系
                    A.push_back(cyc[l]);         // 记录交换的第一个元素
                    B.push_back(cyc[r]);         // 记录交换的第二个元素
                }
            }
    
            // 准备本次循环的交换序列
            reverse(A.begin(), A.end());          // 反转A使其按正确顺序排列
            A.insert(A.end(), B.begin(), B.end()); // 将B添加到A的末尾
    
            // 如果没有需要交换的元素,排序完成
            if (A.empty())
                break;
    
            // 将本次的交换序列加入答案
            ans.push_back(A);
        }
    
        // 断言:最多只需要2轮交换操作
        assert(ans.size() <= 2);
        cout << ans.size() << "\n";
    
        // 输出每轮交换的详细信息
        for (auto z : ans) {
            cout << z.size() << "\n";  // 输出本轮交换操作的数量
            
            // 输出本轮所有要交换的元素索引
            for (auto x : z)
                cout << x << " ";
            
            cout << "\n";
        }
    
        return 0;
    }
    
    • 1

    信息

    ID
    3183
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    2
    已通过
    1
    上传者