描述
题解
这个题可以抽象成一个经典的问题——任意交换两个元素,使给定序列有序化。
官方题解写的十分好,所以这里直接上官方题解吧……(^__^) 嘻嘻……
由于每一步操作可逆,所以从初始状态到目标状态跟目标到初始是等价的。先枚举将英文字母用不重复的数代替的方案。然后,考虑在目标状态中每一个数的后继。注意到在初始状态中对于每一个数是有唯一的合法后继的。每一次操作,可以发现,一定是交换了两个数的后继。反过来,每一次交换两个数的后继,都有唯一的操作与之对应。那么,题目就可以转换为,要将某一后继的排列,通过每次交换两个数的后继,变成某一合法的后继排列的交换次数。
还可以进一步转换,注意到唯一合法后继是一个唯一的排列,那么必然存在一个映射,将这个排列变成有序排列。对目标状态也做这样一个映射,题目就可以转换为将某一个排列,通过每次交换两个数,变成有序的数列的最少交换次数。这就是个经典题了。答案就是总数减去循环节的个数。或者可以用其他奇怪的方法来处理。
看到这里,我赶忙查了查这个经典的算法,因为我竟然没有做过这个经典的算法问题,在网上找了一篇不错的 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;
}