题解:洛谷 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\))次:
- 当前从左往右数在位置 \(A_1\ldots A_2\) 的奶牛序列反转她们的顺序(\(1\le A1<A2\le N\))。
- 然后,在当前从左往右数在位置 \(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
【核心思想】
-
问题分析:有 \(N\) 头奶牛初始编号为 \(1..N\) 排成一排。每次“晨练”包含两个连续操作:先反转区间 \([A_1, A_2]\),再反转区间 \([B_1, B_2]\)。求重复该过程 \(K\) 次(\(K \le 10^9\))后最终排列。由于 \(N \le 100\),排列状态数是有限的(最多 \(N!\) 种),而 \(K\) 巨大,因此核心是利用置换的周期性——找到整个复合操作(一次晨练)的循环节,将 \(K\) 对周期取模,然后模拟剩余次数。
-
算法选择:
- 模拟 + 寻环:先模拟一次完整的晨练(两个反转)作为基本置换 \(P\)。然后从初始排列开始,重复应用 \(P\),直到回到初始排列,记录周期 \(T\)。最后只需模拟 \(K \bmod T\) 次晨练即可得到最终排列。
- 区间反转模拟:直接对数组进行两次区间反转(每次反转用双指针交换),\(N\) 很小,\(O(N)\) 每次。
-
关键步骤:
- 读入与初始化:读入 \(N\)、\(K\)、\(A_1\)、\(A_2\)、\(B_1\)、\(B_2\)。初始化数组 \(c[i] = i\)。
- 寻找周期:
- 循环执行一次完整的晨练(先反转 \(A\) 区间,再反转 \(B\) 区间),每执行一次,
cnt++。 - 每次执行后检查数组是否恢复为 \(1..N\) 的初始状态。若恢复,则周期 \(T = cnt\)(注意代码中
cnt统计的是单个反转次数,一次晨练包含两个反转,所以周期为cnt / 2)。
- 循环执行一次完整的晨练(先反转 \(A\) 区间,再反转 \(B\) 区间),每执行一次,
- 取模并模拟:令
remain = K % (cnt / 2)。然后从初始状态出发,执行remain次完整的晨练(每次两个反转)。 - 输出最终数组:逐行输出 \(c[1..N]\)。
-
时间/空间复杂度:
- 时间复杂度:\(O(N \cdot T)\),其中 \(T\) 为周期(最多 \(N!\) 但实际远小),\(N \le 100\),完全可行。
- 空间复杂度:\(O(N)\)。
-
置换 + 周期取模的核心思想:
- 置换的幂运算:重复执行同一组操作,本质上是排列的复合。对于有限置换,存在最小周期 \(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
浙公网安备 33010602011771号