题解:洛谷 P9951 Swapity Swap

【题目来源】

洛谷:P9951 [USACO20FEB] Swapity Swap B - 洛谷

【题目描述】

Farmer John 的 \(N\) 头奶牛(\(1\le N\le 100\))站成一排。对于每一个 \(1\le i\le N\),从左往右数第 \(i\) 头奶牛的编号为 \(i\)

Farmer John 想到了一个新的奶牛晨练方案。他让她们重复以下包含两个步骤的过程 \(K\)\(1\le K\le 10^9\))次:

  1. 当前从左往右数在位置 \(A_1\ldots A_2\) 的奶牛序列反转她们的顺序(\(1\le A1<A2\le N\))。
  2. 然后,在当前从左往右数在位置 \(B_1\ldots B_2\)的奶牛序列反转她们的顺序(\(1\le B_1<B_2\le N\))。

当奶牛们重复这一过程 \(K\) 次后,请对每一个 \(1\le i\le N\) 输出从左往右数第 \(i\) 头奶牛的编号。

【输入】

输入的第一行包含 \(N\)\(K\)。第二行包含 \(A_1\)\(A_2\),第三行包含 \(B_1\)\(B_2\)

【输出】

在第 \(i\) 行输出晨练结束时从左往右数第 \(i\) 头奶牛的编号。

【输入样例】

7 2
2 5
3 7

【输出样例】

1
2
4
3
5
7
6

【核心思想】

  1. 问题分析:有 \(N\) 头奶牛初始编号为 \(1..N\) 排成一排。每次“晨练”包含两个连续操作:先反转区间 \([A_1, A_2]\),再反转区间 \([B_1, B_2]\)。求重复该过程 \(K\) 次(\(K \le 10^9\))后最终排列。由于 \(N \le 100\),排列状态数是有限的(最多 \(N!\) 种),而 \(K\) 巨大,因此核心是利用置换的周期性——找到整个复合操作(一次晨练)的循环节,将 \(K\) 对周期取模,然后模拟剩余次数。

  2. 算法选择

    • 模拟 + 寻环:先模拟一次完整的晨练(两个反转)作为基本置换 \(P\)。然后从初始排列开始,重复应用 \(P\),直到回到初始排列,记录周期 \(T\)。最后只需模拟 \(K \bmod T\) 次晨练即可得到最终排列。
    • 区间反转模拟:直接对数组进行两次区间反转(每次反转用双指针交换),\(N\) 很小,\(O(N)\) 每次。
  3. 关键步骤

    • 读入与初始化:读入 \(N\)\(K\)\(A_1\)\(A_2\)\(B_1\)\(B_2\)。初始化数组 \(c[i] = i\)
    • 寻找周期
      • 循环执行一次完整的晨练(先反转 \(A\) 区间,再反转 \(B\) 区间),每执行一次,cnt++
      • 每次执行后检查数组是否恢复为 \(1..N\) 的初始状态。若恢复,则周期 \(T = cnt\)(注意代码中 cnt 统计的是单个反转次数,一次晨练包含两个反转,所以周期为 cnt / 2)。
    • 取模并模拟:令 remain = K % (cnt / 2)。然后从初始状态出发,执行 remain 次完整的晨练(每次两个反转)。
    • 输出最终数组:逐行输出 \(c[1..N]\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \cdot T)\),其中 \(T\) 为周期(最多 \(N!\) 但实际远小),\(N \le 100\),完全可行。
    • 空间复杂度:\(O(N)\)
  5. 置换 + 周期取模的核心思想

    • 置换的幂运算:重复执行同一组操作,本质上是排列的复合。对于有限置换,存在最小周期 \(T\),使得 \(P^T = \text{identity}\)。因此 \(P^K = P^{K \bmod T}\)
    • 避免直接模拟大 \(K\):由于 \(K\) 可达 \(10^9\),直接模拟不可行。通过“找环”将指数降至 \(N\) 级别。
    • 反转操作的实现:区间反转可直接用 swap 对称交换,无需复杂数据结构。
    • 周期检查:每次应用置换后检查是否回到初始排列,一旦回到即可停止寻找,保证找到的 \(T\) 为最小正周期。
    • 适用场景:适用于操作可复合且状态空间有限、操作次数巨大的重复模拟问题。核心是发现并利用操作的周期性。

【算法标签】

普及 #模拟

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n, k, c[105];
int a[5], b[5], cnt=0;
// ifstream filein("swap.in");
// ofstream fileout("swap.out");
int main()
{
    // filein >> n >> k;  // 输入n和k
    cin >> n >> k;
    for (int i=1; i<=n; i++) {  // 先创建N头奶牛的c数组
        c[i] = i;
    }
    // filein >> a[1] >> a[2] >> b[1] >> b[2];  // 输入A1、A2、B1和B2
    cin >> a[1] >> a[2] >> b[1] >> b[2];  // 输入A1、A2、B1和B2
    while (true) {  // 先通过while循环找到经历多少次变换后,回到最初c数组的样子
        for (int j=a[1]; j<a[1]+(a[2]-a[1]+1)/2; j++) {  // A1...A2范围的奶牛反转顺序
            swap(c[j], c[a[1]+a[2]-j]);
        }
        cnt++;  // 调整次数加1
        int mark = 0;  // 定义标记位判断是否变回c数组原来的样子
        for (int i=1; i<=n; i++) {
            if (c[i]!=i) {
                mark = 1;
                break;
            }
        }
        if (mark == 0) {  // 如果变回去,就停止while循环
            break;
        }
        for (int j=b[1]; j<b[1]+(b[2]-b[1]+1)/2; j++) {  // B1...B2范围的奶牛反转顺序
            swap(c[j], c[b[1]+b[2]-j]);
        }
        cnt++;  // 调整次数加1
        int mark2 = 0;  // 定义标记位判断是否变回c数组原来的样子
        for (int i=1; i<=n; i++) {
            if (c[i]!=i) {
                mark2 = 1;
                break;
            }
        }
        if (mark2 == 0) {  // 如果变回去,就停止while循环
            break;
        }
    }
    k = k % (cnt/2);  // 对k进行取余计算(cnt必然为偶数,所以可以不用加2次)
    for (int i=1; i<=k; i++) {  // 再按照题意循环k次
        for (int j=a[1]; j<a[1]+(a[2]-a[1]+1)/2; j++) {  // A1...A2范围的奶牛反转顺序
            swap(c[j], c[a[1]+a[2]-j]);
        }
        for (int j=b[1]; j<b[1]+(b[2]-b[1]+1)/2; j++) {  // B1...B2范围的奶牛反转顺序
            swap(c[j], c[b[1]+b[2]-j]);
        }
    }
    for (int i=1; i<=n; i++) {  // 逐行打印调整后的c数组
        // fileout << c[i] << endl;
        cout << c[i] << endl;
    }
    return 0;
}

【运行结果】

7 2
2 5
3 7
1
2
4
3
5
7
6
posted @ 2026-08-30 09:16  团爸讲算法  阅读(6)  评论(0)    收藏  举报