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.
Try not to optimize and separately. The annoying part is only : it appears exactly when your chosen edges create cycles in the graph.
Ask what happens inside one simple cycle. Pick the edge in that cycle with the largest segment length . Can the other edges already cover everything that edge covers on the number line?
Yes. Along the cycle, there is another path between the same two endpoints. The union of intervals on that path covers the whole interval between those endpoints. So deleting the longest edge of a cycle does not decrease .
That means any cyclic chosen set can be improved or kept equal by deleting an edge from a cycle: stays the same, while cannot increase and usually drops. So there is always an optimal answer whose graph is a forest.
Now the problem becomes: choose edges forming a forest and maximize covered union length. Sort edges by decreasing interval length and greedily take an edge if its endpoints are in different DSU components. This is Kruskal, just with interval length as the weight.
The trick is realizing the cycle penalty is basically yelling at you: do not make cycles.
Let's unpack why that is actually safe, because this is where the problem tries to look weirder than it is.
Key Lemma: Cycles Are Useless
Suppose we selected some set of edges and there is a simple cycle in the graph. Take any edge on that cycle. The rest of the cycle gives another path from to :
Each edge of that path is also an interval on the number line. Now here is the important observation:
If you walk from to through these vertices, the intervals
cover the whole interval between and .
Why? Imagine moving along the path. The covered set is always connected enough to include every point between the smallest and largest reached position, and when the walk finally reaches , everything between and has been covered. The path may wiggle left and right, but that only covers more, not less.
So if edge lies on a cycle, the other edges of that cycle already cover its whole number-line segment. Deleting does not decrease .
What about ? Deleting an edge cannot create a new cycle. So cannot increase.
Therefore deleting an edge from a cycle never makes worse. Repeat this until no cycles remain. So there is always an optimal answer that is a forest, meaning .
Nice. The graph half of the problem just got kneecapped.
Now What Are We Maximizing?
Since we only need to consider forests, the objective becomes just maximizing , the total covered length on the number line.
For a forest, we can think of every selected edge as useful in a very direct way. If we process edges from longest to shortest and take only edges that connect different DSU components, this is exactly Kruskal's algorithm for a maximum spanning forest with edge weight
But why does this maximize the union length, not just the sum of edge lengths?
For any connected component that is a tree, the union of its edge-intervals covers the entire interval from the minimum vertex in that component to the maximum vertex in that component. This follows from the same path idea: in a tree, every vertex is connected by paths, and paths cover the intervals between their endpoints.
So a tree component contributes:
to the covered length, except overlapping between different components is naturally counted only once by the global union. Kruskal's greedy choice is still safe because whenever an edge connects two different components, adding the longest available bridge is the best possible way to expand/merge covered ranges before shorter options. This is the usual exchange argument behind maximum spanning forests: if an optimal forest does not contain the current longest safe edge, adding it creates one cycle in that optimal forest plus this edge; some other edge on that cycle can be removed, and it is no longer than the greedy edge. The covered union does not decrease under this swap, because the removed edge's endpoints remain connected through the cycle path.
So the whole solution is:
This produces a forest, so . By the exchange argument, no other forest can give larger covered union length. And since every optimal answer can be turned into a forest without hurting the value, this is globally optimal.
Complexity
There are edges and at most vertices.
Sorting costs:
DSU operations are basically constant amortized time:
Across all test cases this is easily fine under the given constraints. No drama, no cursed DP needed.
#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, sz;
DSU(int n = 0) {
init(n);
}
void init(int n) {
p.resize(n + 1);
sz.assign(n + 1, 1);
iota(p.begin(), p.end(), 0);
}
int find(int x) {
if (p[x] == x) return x;
return p[x] = find(p[x]);
}
bool unite(int a, int b) {
a = find(a);
b = find(b);
if (a == b) return false;
if (sz[a] < sz[b]) swap(a, b);
p[b] = a;
sz[a] += sz[b];
return true;
}
};
struct Edge {
int a, b, id;
};
int main() {
setIO();
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<Edge> edges(n);
for (int i = 0; i < n; i++) {
cin >> edges[i].a >> edges[i].b;
edges[i].id = i + 1;
}
sort(edges.begin(), edges.end(), [](const Edge& x, const Edge& y) {
return x.b - x.a > y.b - y.a;
});
DSU dsu(2 * n);
vector<int> ans;
for (const auto& e : edges) {
if (dsu.unite(e.a, e.b)) {
ans.push_back(e.id);
}
}
cout << ans.size() << '\n';
for (int i = 0; i < (int)ans.size(); i++) {
if (i) cout << ' ';
cout << ans[i];
}
cout << '\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, sz;
DSU(int n = 0) {
init(n);
}
void init(int n) {
p.resize(n + 1);
sz.assign(n + 1, 1);
iota(p.begin(), p.end(), 0);
}
int find(int x) {
if (p[x] == x) return x;
return p[x] = find(p[x]);
}
bool unite(int a, int b) {
a = find(a);
b = find(b);
if (a == b) return false;
if (sz[a] < sz[b]) swap(a, b);
p[b] = a;
sz[a] += sz[b];
return true;
}
};
struct Edge {
int a, b, id;
};
int main() {
setIO();
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<Edge> edges(n);
for (int i = 0; i < n; i++) {
cin >> edges[i].a >> edges[i].b;
edges[i].id = i + 1;
}
sort(edges.begin(), edges.end(), [](const Edge& x, const Edge& y) {
return x.b - x.a > y.b - y.a;
});
DSU dsu(2 * n);
vector<int> ans;
for (const auto& e : edges) {
if (dsu.unite(e.a, e.b)) {
ans.push_back(e.id);
}
}
cout << ans.size() << '\n';
for (int i = 0; i < (int)ans.size(); i++) {
if (i) cout << ' ';
cout << ans[i];
}
cout << '\n';
}
return 0;
}