Appearance
Array Destruction -- CF 1474C
Difficulty: 1700 * Techniques: greedy, constructive, enumeration, multiset Problem Link
Quick Revisit
- PROBLEM: Pair up 2n elements so each pair sums to x, with x decreasing as the max of each pair
- KEY INSIGHT: The largest element must be in the first pair, so x = a_max + a_j for only O(n) candidates
- TECHNIQUES: greedy (forced pairing), candidate enumeration, multiset
- TRANSFER: When a process is monotone (only decreases), the largest element pins the first move -- enumerate partners
First Read
We have
The constraint
The Greedy Trap
My first thought: "Sort the array, greedily pair the largest unused element with whatever sums to
If I just try random values of
The Key Constraint
The largest element in the array must be used in the very first step (because after that,
That means
- Sort descending. The largest unused element must pair with
(look it up). - Remove both. New
= largest element just used. - Repeat.
If at any point the partner doesn't exist, this
Why Greedy Works Once x Is Fixed
Once
Complexity
Implementation Notes
- Use a
multisetso you can handle duplicate values.erase(find(val))removes exactly one copy. - When pairing, be careful: if
, you need two copies of that value in the multiset. - Store the pairs as you go so you can output them if the simulation succeeds.
- Presentation nit:
tryXreturns{}on failure, andn = 0would also produce{}. The constraint guarantees, so it works here. In a more general setting, return optional<vector<pair<int,int>>>(or aboolplus the vector) so success and emptiness are distinguishable.
The Code
cpp
#include <bits/stdc++.h>
using namespace std;
void solve() {
int n; cin >> n;
vector<int> a(2 * n);
for (auto& x : a) cin >> x;
sort(a.rbegin(), a.rend());
auto tryX = [&](int x) -> vector<pair<int,int>> {
multiset<int> ms(a.begin(), a.end());
vector<pair<int,int>> res;
int cur = x;
for (int step = 0; step < n; step++) {
int big = *ms.rbegin();
ms.erase(ms.find(big));
int need = cur - big;
auto it = ms.find(need);
if (it == ms.end()) return {};
ms.erase(it);
res.push_back({big, need});
cur = big;
}
return res;
};
for (int i = 1; i < 2 * n; i++) {
int x = a[0] + a[i];
auto res = tryX(x);
if (!res.empty()) {
cout << "YES\n" << x << "\n";
for (auto [a, b] : res) cout << a << " " << b << "\n";
return;
}
}
cout << "NO\n";
}
int main() {
int t; cin >> t;
while (t--) solve();
}Transfer Lesson
- "The largest element is forced" -- when a process is monotone (values only decrease), the biggest element pins down the first move, massively reducing the search space.
- Enumerate a small set of candidates, simulate each -- a powerful pattern when brute force over all possibilities is too much, but fixing one decision makes the rest forced.
- Multiset for pairing problems --
erase(find(...))to remove one copy,rbegin()for the current max. Clean and fast.
See also: Greedy | Constructive Algorithms | Set and Multiset | Pattern Recognition Guide | Practice Ladder 1400-1700