Codeforces 441D Valera and Swaps(置换群)

题意:

  给定一个1~n的排列(n<=3000),输出字典序最小且次数最少的交换操作,使得操作后的排列可以通过最少m次交换得到排列[1,2,...n]

 

 

Solution:

  可以将排列的对应关系看做边,f[i]=i,代表自环。那么根据置换原理,图中有k个环,则需要最少n-k次交换操作得到排列[1,2...n]。所以,先找出图中的环,对同一个环的位置进行标记。这样对不在同一个环的两个位置进行交换,会将两个环合并。将在同一个环内的两个位置进行交换,会将这个环分成两个环。

     只需要,判断需要加环还是去环。贪心选择序号较小的位置即可。

 

/*

    置换群

*/

#include <bits/stdc++.h>

using namespace std;



const int MAXN = 3009;



int f[MAXN], g[MAXN], pos[MAXN];



int n, m, ans, sum, t;

int  main() {

    scanf ("%d", &n);

    for (int i = 1; i <= n; i++)

        scanf ("%d", &g[i]), pos[g[i]] = i;



    scanf ("%d", &m);



    for (int i = 1; i <= n; i++)

        if (!f[i]) {

            f[i] = ++sum;

            for (int x = i; !f[g[x]]; x = g[x])    f[g[x]] = f[i];

        }



    t = n - sum;

    printf ("%d\n", m - t > 0 ? m - t : t - m);



    for (int i = 2; t < m; i++)

        if (f[1] != f[i]) {

            t++;

            for (int x = i; f[x] != f[1]; x = g[x])        f[x] = f[1];

            printf ("1 %d ", i);

        }



    for (int i = 1; t > m; i++) {

        if (g[i] != i)

            for (int j = i + 1; j <= n && t > m; j++)

                if (f[i] == f[j]) {

                    printf ("%d %d ", i, j);

                    swap (g[i], g[j]);

                    t--;

                    if (g[i] == i) {

                        f[i] = -1; break;

                    }

                    else

                        f[i] = f[g[i]];



                    if (g[j] == j)  f[j] = -1;

                    else {

                        f[j] = ++sum;

                        for (int x = j; f[g[x]] != sum; x = g[x])        f[g[x]] = sum;

                    }

                }

    }

    return 0;

}
View Code

 

你可能感兴趣的:(codeforces)