2020ICPC昆明区域赛J. Parallel Sort题解
J. Parallel Sort
题意
给定一个排列,每轮可以对这个排列选择若干不相交的数对,将其一起交换,问最少需要多少轮交换才能将排列排序成升序状态,并输出方案。
分析
由于排序相当于一个置换,而置换可以唯一表示为不相交的轮换之积,所以我们只需要研究轮换即可。
对于\(1-轮换\),显然不需要操作。
对于\(2-\text{轮换}\),也叫对换,显然可以在一次之内搞定,两者交换一下即可。
对于\(n-\text{轮换}(n>2)\),一次无法搞定,因为\(n-\text{轮换}(n>2)\)肯定无法写成不相交的对换之积。由于对于所有的轮换都是相似的,我们不妨考虑以下轮换。
\[\left(
\begin{array}{l}
1&2&3&\cdots&n-2&n-1&n\\
2&3&4&\cdots&n-1&n&1
\end{array}
\right)
\]
上面这个置换表示的意思是\(1\)要变成\(2\),\(2\)要变成\(3\),\(3\)要变成\(4\),\(\dots\),\(n-2\)要变成\(n-1\),\(n-1\)要变成\(n\),\(n\)要变成\(1\)。
我们怎么样用尽量少的操作完成这个置换,\(1\)轮操作不行,我们尝试\(2\)轮操作。
很容易发现,在\(1\)轮操作之内是可以将整个序列逆序的,只需要第\(1\)个和第\(n\)个交换,第\(2\)个和第\(n-1\)个交换,\(\dots\),第\(\lfloor n/2 \rfloor\)个和第\(n+1-\lfloor n/2 \rfloor\)个交换即可。发现这个性质之后,我们很容易在\(2\)轮操作之内实现上面的置换。
第\(1\)轮,将整个序列逆序,得到
\[\left[
\begin{array}{l}
n&n-1&n-2&\cdots&3&2&1\\
\end{array}
\right]
\]
第\(2\)轮,将序列的前\(n-1\)个元素构成的子序列逆序,得到
\[\left[
\begin{array}{l}
2&3&4&\cdots&n-1&n&1\\
\end{array}
\right]
\]
虽然只说明了上面这个轮换有这个性质,但是不难发现对于所有轮换都有这个性质。
故我们可以先将要执行的置换拆成不相交轮换之积,对每个轮换单独按以上述方式处理即可。
代码
#include <cstdio>
const int maxn = 1e5 + 10;
int n;
int vis[maxn], pos[maxn];
int tmp[maxn], tot_tmp;
int ans[2][maxn], tot[2];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
int t;
scanf("%d", &t);
pos[t] = i;
}
for (int i = 1; i <= n; i++) {
if (vis[i]) continue;
tot_tmp = 0;
vis[i] = 1;
tmp[tot_tmp++] = i;
for (int j = pos[i]; j != i; j = pos[j]) {
vis[j] = 1;
tmp[tot_tmp++] = j;
}
if (tot_tmp == 2) {
ans[0][tot[0]++] = tmp[0];
ans[0][tot[0]++] = tmp[1];
} else if (tot_tmp > 2) {
for (int j = 0; j < tot_tmp / 2; j++) {
ans[0][tot[0]++] = tmp[j];
ans[0][tot[0]++] = tmp[tot_tmp - 1 - j];
}
for (int j = 0; j < (tot_tmp - 1) / 2; j++) {
ans[1][tot[1]++] = tmp[j];
ans[1][tot[1]++] = tmp[tot_tmp - 2 - j];
}
}
}
if (tot[0] == 0) {
puts("0");
} else {
int m = tot[1] ? 2 : 1;
printf("%d\n", m);
for (int i = 0; i < m; i++) {
printf("%d", tot[i] / 2);
for (int j = 0; j < tot[i]; j++) {
printf(" %d", ans[i][j]);
}
puts("");
}
}
return 0;
}