ACM模版

描述


题解

这个题可以抽象成一个经典的问题——任意交换两个元素,使给定序列有序化。

官方题解写的十分好,所以这里直接上官方题解吧……(^__^) 嘻嘻……

由于每一步操作可逆,所以从初始状态到目标状态跟目标到初始是等价的。先枚举将英文字母用不重复的数代替的方案。然后,考虑在目标状态中每一个数的后继。注意到在初始状态中对于每一个数是有唯一的合法后继的。每一次操作,可以发现,一定是交换了两个数的后继。反过来,每一次交换两个数的后继,都有唯一的操作与之对应。那么,题目就可以转换为,要将某一后继的排列,通过每次交换两个数的后继,变成某一合法的后继排列的交换次数。 


还可以进一步转换,注意到唯一合法后继是一个唯一的排列,那么必然存在一个映射,将这个排列变成有序排列。对目标状态也做这样一个映射,题目就可以转换为将某一个排列,通过每次交换两个数,变成有序的数列的最少交换次数。这就是个经典题了。答案就是总数减去循环节的个数。或者可以用其他奇怪的方法来处理。

看到这里,我赶忙查了查这个经典的算法,因为我竟然没有做过这个经典的算法问题,在网上找了一篇不错的 blog,膜了一发,学习了一下,果然有趣!

代码

#include <iostream>
#include <algorithm>
#include <cstdio>

using namespace std;

const int MAXN = 30;
const int INF = 0x3f3f3f3f;

char s[MAXN];
int vis[MAXN];
int A[MAXN], B[MAXN], C[MAXN], D[MAXN];

int main()
{
    int N, M, K;
    while (cin >> N >> M >> K)
    {
        while (K--)
        {
            for (int i = N + 1; i <= N + M; i++)    // 建立 r 的不重复映射
            {
                B[i] = i;
            }

            int cnt = N + 1;
            for (int i = 1; i <= N + M; i++)
            {
                scanf("%s", s);
                if (s[0] == 'r')
                {
                    A[i] = cnt++;
                }
                else
                {
                    sscanf(s, "%d", &A[i]);
                }
            }

            int ans = INF;
            do
            {
                for (int i = 1; i <= N + M; i++)    // 更替 r 映射顺序
                {
                    C[i] = A[i] <= N ? A[i] : B[A[i]];
                }
                for (int i = 1; i <= N + M; i++)    // 建立后继数组
                {
                    D[C[i]] = C[i % (N + M) + 1];
                    vis[i] = 0;
                }
                for (int i = 1; i <= N + M; i++)    // 将后继数组更替为循环节数组
                {
                    D[i] = (D[i] + N + M - 2) % (N + M) + 1;
                }

                int cnt = N + M;
                for (int i = 1; i <= N + M; i++)    // 查找循环节
                {
                    if (vis[i])
                    {
                        continue;
                    }
                    vis[i] = 1;
                    cnt--;
                    for (int j = D[i]; j != i; j = D[j])
                    {
                        vis[j] = 1;
                    }
                }
                if (cnt & 1)    // 必须为偶数方可
                {
                    continue;
                }
                ans = min(ans, cnt);
            } while (next_permutation(B + N + 1, B + N + M + 1));   // 下一个排列

            printf("%d\n", ans);
        }
    }

    return 0;
}

参考

《使序列有序的最少交换次数》