参考https://www.cnblogs.com/lqerio/p/13485331.html
循环群,do while的那个循环就是在找可以交换的循环,如下图
解释在代码里
#include<iostream>
#include<vector>
using namespace std;
int main() {
int N;
while (cin >> N) {
if (N == 0) {
return 0;
}
vector<int> origin(N); // 原始序列(只是为了好理解而存在,其实根本不需要)
vector<int> target(N); // 目标序列
vector<int> position(65538); // 记录目前数字对应的位置 即保存映射:origin[i] -> i
vector<bool> finded(N, false);
int cost = 0;
int globalMin = 65536;
for (int i = 0; i < N; ++i) {
cin >> origin[i];
position[origin[i]] = i;
globalMin = min(globalMin, origin[i]);
cost += origin[i]; // 每个数至少交换一次
}
cout << cost << endl;
for (int i = 0; i < N; ++i) {
cin >> target[i];
}
for (int i = 0; i < N; ++i) {
if (!finded[i]) {
int insideMin = 65536, circleIndex = i, circleSize = 0;
do {
insideMin = min(insideMin, target[circleIndex]);
finded[circleIndex] = true; // 这个数target[circleIndex]换好了,已经放到circleIndex的位置上了
// 扩展循环
circleIndex = position[target[circleIndex]]; // 找到这个数的原来位置上的数的当前下标
circleSize++;
} while (circleIndex != i); // 直到找到这个循环的闭合
// 耗费计算方法1
// 用循环内的最小数来交换,交换(circleSize - 1) 次,但由于开始的时候cost已经加过一遍所有数了,所以是
// 相当于进行了(circleSize - 2)次对insideMin进行交换
int costA = insideMin * (circleSize - 2); // 如果circleSize为1的话,说明该数字已经在最终位置上了,costA为负,抵消了之前加的origin[i]
// 耗费计算方法2
// 用全局最小数来换,首先要把全局最小数放(交换)入循环用于替换局部最小数,等全部换完之后,再把局部最小数换回来
// globalMin要换circleSize+1次,再加上把局部最小数换回来的额外1次(不是2次,因为开始的时候cost已经加了一遍所有数了)
int costB = globalMin * (circleSize + 1) + insideMin;
// 取最小的耗费,完成这个循环群的替换
cost += min(costA, costB);
}
}
cout << cost << endl;
}
return 0;
}