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;
}
posted @ 2021-12-09 11:19  聆竹听风  阅读(101)  评论(0)    收藏  举报