Search for a command to run...
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Open only as much as you need to keep the solve alive.
Forget the original positions for a moment. In one operation, every value with frequency keeps exactly one copy at and sends the other copies to . Which copy survives only matters for identity, not for the multiset.
Since only the multiset matters, sort the array. You are also allowed to choose a convenient tie-breaking rule for equal values. Pick the rule: among equal values, the rightmost copy is the one that does not move.
First solve the stricter version : run the process until all values are distinct. With the rightmost-survives rule, process the sorted array from right to left. The final value of is the smallest unused integer .
For a fixed element, its value over time is boring in the best possible way: it goes , then stays at . So after operations its value is .
The maximum frequency never increases after an operation, so “after operations every value appears at most times” is monotonic. Binary search , build all values , and check the largest frequency.
The process looks positional, but the first useful move is to stop staring at positions like they personally betrayed you.
For any value with frequency in the current array, one copy of stays at , and the other copies become . The original order only decides which identity survives. The resulting multiset is the same.
So we can sort the array and choose whatever tie-breaking rule makes the math clean.
Choose the right tie-breaker
Sort increasingly. When several elements currently have the same value, pretend the rightmost one is the copy that stays, and all earlier copies move.
This does not change the multiset after each operation. It only relabels identical drones. That is legal because the answer only depends on frequencies.
Now solve a stronger endpoint: what happens if we keep going until all values are distinct? This is the final state.
Let be the final value of sorted element under our rightmost-survives rule.
Process elements from right to left:
So:
This can be computed with a DSU “next free position” structure, classic parking-lot stuff.
What is the value after exactly operations?
Under this tie-breaking rule, element just walks upward until it reaches :
Therefore after operations its value is
That is the key formula. No simulation. No drama.
Why is this true? If the element has not reached its final value yet, the value it wants to stay on is still occupied by some element to its right whose final value is there, so it gets pushed one step. Once it reaches , nobody to its right will claim the same final value, so it stays forever.
Why binary search works
We need the first time when every value appears at most times.
The important monotonicity: the maximum frequency never increases after an operation.
Suppose a value has copies after an operation. They come from two places:
If had copies, at most moved. If had any copies, only one stayed. So the new count of is at most the previous maximum frequency. Thus once all frequencies are , they stay that way.
So the predicate
is monotonic in .
Binary search the smallest valid .
For a check, build all values and count frequencies. Values never exceed about , since and an element moves at most steps. A plain frequency array is enough.
Algorithm
For each test case:
Complexity
Sorting costs . The DSU work is almost linear. Each binary-search check is , and there are checks.
Total:
per test case, with linear memory. Across all tests, this fits easily because .
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct DSU {
vector<int> p;
DSU(int n = 0) { init(n); }
void init(int n) {
p.resize(n + 1);
iota(p.begin(), p.end(), 0);
}
int find(int x) {
if (p[x] == x) return x;
return p[x] = find(p[x]);
}
void erase_pos(int x) {
p[x] = find(x + 1);
}
};
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n, k;
cin >> n >> k;
vector<int> a(n), b(n);
for (int &x : a) cin >> x;
sort(a.begin(), a.end());
int lim = 3 * n + 5;
DSU dsu(lim + 1);
for (int i = n - 1; i >= 0; --i) {
b[i] = dsu.find(a[i]);
dsu.erase_pos(b[i]);
}
vector<int> cnt(lim + 2, 0), touched;
auto ok = [&](int m) {
touched.clear();
bool good = true;
for (int i = 0; i < n; ++i) {
int v = min(a[i] + m, b[i]);
if (cnt[v] == 0) touched.push_back(v);
++cnt[v];
if (cnt[v] > k) good = false;
}
for (int v : touched) cnt[v] = 0;
return good;
};
int lo = 0, hi = n;
while (lo < hi) {
int mid = (lo + hi) / 2;
if (ok(mid)) hi = mid;
else lo = mid + 1;
}
cout << lo << '\n';
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct DSU {
vector<int> p;
DSU(int n = 0) { init(n); }
void init(int n) {
p.resize(n + 1);
iota(p.begin(), p.end(), 0);
}
int find(int x) {
if (p[x] == x) return x;
return p[x] = find(p[x]);
}
void erase_pos(int x) {
p[x] = find(x + 1);
}
};
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n, k;
cin >> n >> k;
vector<int> a(n), b(n);
for (int &x : a) cin >> x;
sort(a.begin(), a.end());
int lim = 3 * n + 5;
DSU dsu(lim + 1);
for (int i = n - 1; i >= 0; --i) {
b[i] = dsu.find(a[i]);
dsu.erase_pos(b[i]);
}
vector<int> cnt(lim + 2, 0), touched;
auto ok = [&](int m) {
touched.clear();
bool good = true;
for (int i = 0; i < n; ++i) {
int v = min(a[i] + m, b[i]);
if (cnt[v] == 0) touched.push_back(v);
++cnt[v];
if (cnt[v] > k) good = false;
}
for (int v : touched) cnt[v] = 0;
return good;
};
int lo = 0, hi = n;
while (lo < hi) {
int mid = (lo + hi) / 2;
if (ok(mid)) hi = mid;
else lo = mid + 1;
}
cout << lo << '\n';
}
return 0;
}