题解:学而思编程 懒惰的猫咪

【题目来源】

学而思编程:懒惰的猫咪

【题目描述】

小猴有 \(n\) 只猫咪,编号从 \(1\)\(n\)。每只猫咪都有自己的地盘,初始时第 \(i\) 只猫咪在第 \(i\) 个位置上。它们在自己的位置待了很长时间了,已经产生厌倦,所以小猴想重新安排它们的位置,这样就不会有猫咪和之前一样还在同一个位置。但是小猴也很懒,所以他想尽量缩短移动的总距离。

例如,有 \(3\) 只猫咪,初始时它们的位置分别是 \(1,2,3\)\(1\) 号猫咪向右移动两个位置,\(2\) 号猫咪向左移动一个位置,\(3\) 号猫咪向左移动一个位置。经过移动之后猫咪们的位置分别是 \(2,3,1\),并且这个移动的总距离为 \(2 + 1 + 1=4\)

请你帮助小猴计算一下移动所有猫咪的最短总距离,并按照从左到右的位置顺序输出每一个位置上最后是那一只猫咪,如果有多种移动方式,输出其中字典序最小的一种。

【输入】

第一行包含整数 \(T\),表示共有 \(T\) 组测试数据。

每组测试数据,一行一个整数 \(n\)

【输出】

每组数据第一行一个整数,表示移动所有猫咪的最短总距离;

第二行 \(n\) 个整数,第 \(i\) 个整数表示移动之后第 \(i\) 个位置上猫咪的编号。

【输入样例】

2
2
3

【输出样例】

2
2 1
4
2 3 1

【核心思想】

  1. 问题分析:给定 \(n\) 只猫咪,初始位置 \(i\) 上是第 \(i\) 号猫咪。要求重新安排使得没有猫咪留在原位(错位排列),且移动总距离最短。若多种方案,输出字典序最小的排列。这是一个构造错位排列 + 最小距离优化问题,关键在于利用相邻交换实现最小移动距离,并分类处理奇偶性。

  2. 算法选择

    • 相邻交换策略:相邻两猫交换位置,每只移动距离为 \(1\),总距离最小
    • 奇偶分类构造\(n\) 为偶数时完全相邻交换;\(n\) 为奇数时,前 \(n-3\) 个相邻交换,最后 \(3\) 个特殊循环
  3. 关键步骤

    • 初始化:读取 \(T\)(测试组数)
    • 处理每组数据
      • 读取 \(n\)
      • \(n = 1\)
        • 输出 1(距离)
        • 输出 1(唯一排列)
      • \(n\) 为偶数
        • 输出 n(总距离 = \(n/2 \times 2 = n\),每对交换距离和为 \(2\)
        • 遍历 \(i\)\(1\)\(n-1\),步长 \(2\)
          • 输出 i+1, i:相邻交换
      • \(n\) 为奇数且 \(n > 1\)
        • 输出 n + 1(总距离,最后 \(3\) 个特殊排列距离为 \(4\)
        • 遍历 \(i\)\(1\)\(n-3\),步长 \(2\)
          • 输出 i+1, i:前 \(n-3\) 个相邻交换
        • 输出 n-1, n, n-2:最后 \(3\) 个循环移位(\(n-2 \to n-1 \to n \to n-2\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(T \times n)\),每组数据线性输出
    • 空间复杂度:\(O(1)\),仅需常数变量
  5. 构造策略的核心思想

    • 最小距离原理:相邻交换使每只猫咪移动距离为 \(1\),是单步最小移动,总距离 \(n\)(偶数)为理论最小
    • 奇数处理:奇数时无法完全配对,最后 \(3\) 个元素需循环移位(\(a \to b \to c \to a\)),距离为 \(4\)\(1+1+2\)\(1+2+1\)),总距离 \(n+1\)
    • 字典序最小:相邻交换从 \(1\) 开始,保证前面的数尽可能小;最后 \(3\) 个用 n-1, n, n-2 而非其他循环排列,确保字典序最小
    • 错位排列:所有猫咪都不在原位,满足题意
    • 适用于"错位排列 + 距离最小化"类问题,相邻交换是构造最优解的贪心策略

【算法标签】

模拟

【代码详解】

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

int T;  // 测试用例数量
int n;  // 数组大小

int main()
{
    cin >> T;  // 输入测试用例数量

    while (T--)  // 处理每个测试用例
    {
        cin >> n;  // 输入数组大小n

        if (n == 1)  // 特殊情况:n=1
        {
            cout << 1 << endl;  // 输出逆序对数量
            cout << 1 << endl;  // 输出排列
        }
        else if (n % 2 == 0)  // 情况2:n为偶数
        {
            cout << n << endl;  // 输出逆序对数量

            // 生成排列:相邻交换
            for (int i = 1; i < n; i += 2)
            {
                cout << i + 1 << " " << i << " ";  // 输出交换后的相邻对
            }
            cout << endl;  // 换行
        }
        else  // 情况3:n为奇数且n>1
        {
            cout << n + 1 << endl;  // 输出逆序对数量

            // 处理前n-3个数:相邻交换
            for (int i = 1; i < n - 2; i += 2)
            {
                cout << i + 1 << " " << i << " ";  // 输出交换后的相邻对
            }

            // 处理最后三个数:特殊排列
            cout << n - 1 << " " << n << " " << n - 2 << endl;
        }
    }

    return 0;  // 程序正常结束
}

【运行结果】

2
2
2
2 1
3
4
2 3 1
posted @ 2026-07-05 14:46  团爸讲算法  阅读(6)  评论(0)    收藏  举报