1 条题解
-
0
「EGOI2021」卢娜爱磕 cp 题解
题目分析
我们有 个人排成一队,每对相同数字的人是一对情侣。可以进行两种操作:
- 交换相邻的两个人(花费 1 次操作)
- 移除相邻的一对情侣(花费 1 次操作)
目标是找到移除所有情侣的最小操作次数。
关键观察
1. 问题转化
这个问题可以类比为括号匹配问题:
- 每对情侣就像一对括号
- 移除相邻情侣就像消除匹配的括号
- 交换操作就像调整括号位置
2. 重要性质
- 移除操作不花费交换次数:一旦情侣相邻,移除是免费的(只算 1 次操作)
- 连锁反应:移除一对情侣可能使其他情侣自动相邻
- 贪心策略:应该优先移除那些"容易"配对的情侣
算法思路
方法一:贪心模拟(适用于小数据)
对于 ,可以模拟整个过程:
#include <iostream> #include <vector> #include <list> using namespace std; int main() { int n; cin >> n; vector<int> arr(2 * n); for (int i = 0; i < 2 * n; i++) { cin >> arr[i]; } list<int> seq(arr.begin(), arr.end()); int operations = 0; while (!seq.empty()) { bool found = false; // 查找相邻的情侣 auto it = seq.begin(); auto next_it = it; next_it++; while (next_it != seq.end()) { if (*it == *next_it) { // 找到相邻情侣,移除他们 seq.erase(it); seq.erase(next_it); operations++; // 移除操作 found = true; break; } it++; next_it++; } if (!found) { // 没有相邻情侣,需要交换 // 找到距离最近的一对情侣 it = seq.begin(); int min_dist = 2 * n; auto best_it = seq.begin(); for (; it != seq.end(); it++) { auto find_it = it; find_it++; int dist = 1; while (find_it != seq.end()) { if (*find_it == *it) { if (dist < min_dist) { min_dist = dist; best_it = it; } break; } find_it++; dist++; } } // 交换使这对情侣相邻 auto target = best_it; target++; int temp = *best_it; *best_it = *target; *target = temp; operations++; // 交换操作 } } cout << operations << endl; return 0; }方法二:栈匹配 + 位置分析(适用于大数据)
更高效的方法是利用栈的思想:
#include <iostream> #include <vector> #include <stack> using namespace std; int main() { int n; cin >> n; vector<int> arr(2 * n); for (int i = 0; i < 2 * n; i++) { cin >> arr[i]; } vector<int> first_occurrence(n + 1, -1); vector<bool> removed(2 * n, false); int operations = 0; int remaining = 2 * n; while (remaining > 0) { // 使用栈来匹配相邻情侣 stack<pair<int, int>> st; // (value, index) vector<bool> in_stack(2 * n, false); for (int i = 0; i < 2 * n; i++) { if (removed[i]) continue; if (!st.empty() && st.top().first == arr[i]) { // 找到匹配,移除这对情侣 removed[st.top().second] = true; removed[i] = true; operations++; remaining -= 2; st.pop(); } else { st.push({arr[i], i}); } } if (remaining == 0) break; // 如果没有自动匹配的,找到距离最近的一对进行交换 int min_dist = 2 * n; int swap_pos = -1; for (int i = 0; i < 2 * n; i++) { if (removed[i]) continue; for (int j = i + 1; j < 2 * n; j++) { if (!removed[j] && arr[i] == arr[j]) { if (j - i - 1 < min_dist) { min_dist = j - i - 1; swap_pos = i; } break; } } } if (swap_pos != -1) { // 执行一次交换 operations++; // 找到要交换的位置 for (int i = swap_pos; i < 2 * n - 1; i++) { if (!removed[i] && !removed[i + 1] && arr[i] != arr[i + 1]) { swap(arr[i], arr[i + 1]); break; } } } } cout << operations << endl; return 0; }最优解法:O(n) 方法
实际上,这个问题有更深刻的组合数学性质:
#include <iostream> #include <vector> using namespace std; int main() { int n; cin >> n; vector<int> arr(2 * n); for (int i = 0; i < 2 * n; i++) { cin >> arr[i]; } vector<int> pos(n + 1, -1); int operations = 0; for (int i = 0; i < 2 * n; i++) { if (pos[arr[i]] == -1) { // 第一次出现,记录位置 pos[arr[i]] = i; } else { // 第二次出现,计算需要的交换次数 // 这对情侣之间的不同数字个数就是需要交换的次数 int first_pos = pos[arr[i]]; int count = 0; for (int j = first_pos + 1; j < i; j++) { if (arr[j] != 0) { // 0 表示已移除 count++; } } operations += count; // 标记为已移除 arr[first_pos] = arr[i] = 0; } } // 总操作次数 = 交换次数 + n(n次移除操作) cout << operations + n << endl; return 0; }样例分析
样例1:
3 1 2 1 2 3- 初始:
[3, 1, 2, 1, 2, 3] - 计算每对情侣之间的障碍物数量
- 总交换次数 = 1,移除操作 = 3
- 总操作次数 = 1 + 3 = 4
样例2:
5 1 2 3 2 3 1 4 5 4- 类似计算得到总操作次数 = 7
算法正确性
- 贪心最优性:每次消除"最容易"的情侣是最优策略
- 交换次数计算:一对情侣之间的不同数字个数就是使他们相邻所需的最小交换次数
- 移除操作计数:每对情侣都需要一次移除操作
复杂度分析
- 时间复杂度: 对于模拟方法, 对于最优方法
- 空间复杂度:
- 支持范围:
总结
这道题的关键在于:
- 将问题转化为类似括号匹配的消除问题
- 发现交换次数等于情侣间障碍物数量的性质
- 使用贪心策略按顺序处理情侣
最优解法利用了组合数学的性质,通过计算每对情侣之间的"障碍"数量来直接得出答案。
- 1
信息
- ID
- 4058
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者