时间限制:C/C++ 1000MS,其他语言 2000MS
内存限制:C/C++ 128MB,其他语言 256MB
难度:困难
分数:100 OI排行榜得分:16(0.1分数+2难度)
描述
n个学生排成一排,学生编号分别是1到n,n为3的整倍数。
老师随机抽签决定将所有学生分成个3人的小组,n=3*m
为了便于同组学生交流,老师决定将小组成员安排到一起,也就是同组成员彼此相连,同组任意两个成员之间无其它组的成员。
因此老师决定调整队伍,老师每次可以调整任何一名学生到队伍的任意位置,计为调整了一次,请计算最少调整多少次可以达到目标。
注意:对于小组之间没有顺序要求,同组学生之间没有顺序要求
输入描述
两行字符串,空格分隔表示不同的学生编号。
第一行是学生目前排队情况第二行是随机抽签分组情况,从左开始每3个元素为一组n为学生的数量,n的范围为[3,900],n一定为3的整数倍。第一行和第二行的元素个数一定相同。
输出描述
老师调整学生达到同组彼此相连的最小次数
用例输入 1 **
7 9 8 5 6 4 2 1 3
7 8 9 4 2 1 3 5 6
用例输出 1 **
1
提示
学生目前排队情况:7 9 8 5 6 4 2 1 3
学生分组情况:[7 8 9]、[4 2 1]、[3 5 6]
将3调整到4之前,队列调整为7 9 8 5 6 3 4 2 1
那么三个小组成员均彼此相连[7 9 8]、[5 6 3]、[4 2 1]
输出:1
#include <iostream>
#include <vector>
#include <unordered_map>
#include <cstdlib>
#include <ctime>
#include <algorithm>
#include <sstream>
using namespace std;
// 用于检查第idx组中的三个元素是否完全相同
bool checkGroup(const vector<int>& nums, int idx) {
return nums[idx * 3] == nums[idx * 3 + 1] && nums[idx * 3 + 2] == nums[idx * 3 + 1];
}
// 用于检查nums是否已经排好队的函数
// 如果每3个元素都相等,则说明这一组已经排好
bool checkAll(const vector<int>& nums, int startIdx, int endIdx) {
for (int idx = startIdx; idx <= endIdx; ++idx) {
if (!checkGroup(nums, idx)) {
return false;
}
}
return true;
}
// 更新尚未排好序的组的区间startGroup和endGroup
pair<int, int> updateGroupInterval(const vector<int>& nums, int startGroup, int endGroup) {
int i = startGroup, j = endGroup;
while (checkGroup(nums, i)) {
i++;
}
while (checkGroup(nums, j)) {
j--;
}
return {i, j};
}
// 用于进行蒙特卡洛模拟的函数
int mcSimulation(vector<int>& nums, int n) {
// 蒙特卡洛模拟的次数,该数值可以自行进行调整
int T = 10000;
// 初始化最多的调整次数,该数值可以自行进行调整
int ans = 10;
// T次蒙特卡洛模拟
for (int t = 0; t < T; ++t) {
// 每一次蒙特卡洛模拟,都需要重新获得原nums的副本
// 对副本numsNew进行排队的模拟
vector<int> numsNew = nums;
// 移动的次数最多不超过ans次数
int time = 0;
// 尚未排好序的组的区间
int startIdx = 0, endIdx = n - 1;
while (time < ans) {
// 本次模拟的移动次数+1
time++;
// 更新尚未排好序的组的区间
auto interval = updateGroupInterval(nums, startIdx, endIdx);
startIdx = interval.first;
endIdx = interval.second;
// 随机选择组编号group_i,是取出的索引i所在的组
int groupI = rand() % (endIdx - startIdx + 1) + startIdx;
// 计算得到取出的位置i
int i = groupI * 3 + rand() % 3;
// 随机选择组编号group_j,是取出的索引j所在的组
int groupJ;
do {
groupJ = rand() % (endIdx - startIdx + 1) + startIdx;
} while (groupJ == groupI);
// 计算得到插入的位置j
int j = groupJ * 3 + rand() % 3;
// 取出索引i位置的元素num
int num = numsNew[i];
numsNew.erase(numsNew.begin() + i);
numsNew.insert(numsNew.begin() + j, num);
// 判断numsNew是否已经排好队
// 如果是则直接更新ans
if (checkAll(numsNew, startIdx, endIdx)) {
ans = time;
break;
}
}
}
return ans;
}
int main() {
srand(time(0));
// 输入两个数组
vector<int> nums1, nums2;
string input;
getline(cin, input);
istringstream iss1(input);
int num;
while (iss1 >> num) {
nums1.push_back(num);
}
getline(cin, input);
istringstream iss2(input);
while (iss2 >> num) {
nums2.push_back(num);
}
// m个同学
int m = nums1.size();
// n个组
int n = m / 3;
// 构建映射哈希表
unordered_map<int, int> map;
// 对于nums2,每三个元素映射到组编号i
for (int i = 0; i < n; ++i) {
for (int j = 0; j < 3; ++j) {
map[nums2[i * 3 + j]] = i;
}
}
// 根据映射哈希表,构建数组nums
// nums数组包含0至(n//3-1)一共n//3个元素
// 每一个元素出现3次
// 形如:[0, 0, 0, 1, 1, 2, 2, 2, 1]
vector<int> nums(m);
for (int i = 0; i < m; ++i) {
nums[i] = map[nums1[i]];
}
// 判断数组nums是否已经是已排好队的
// 如果是则直接输出0,否则进行蒙特卡洛模拟过程
if (checkAll(nums, 0, n - 1)) {
cout << 0 << endl;
} else {
cout << mcSimulation(nums, n) << endl;
}
return 0;
}