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.
Treat detonating creeper as choosing the interval . This is almost minimum interval cover, except selected interval centers can kill each other, which is the spicy bit.
Look at the selected creepers in increasing position order. In an optimal solution, each chosen creeper should extend the killed prefix farther right; otherwise it can be deleted from the chosen set and nothing gets worse.
For two consecutive chosen creepers , they are safe if either 's explosion does not reach , or 's explosion does not reach . In formulas, either , or .
Let a segment tree position store the minimum detonations needed to kill the prefix using only processed centers. For creeper , you only need two range-min queries: one for previous endpoints , and one for endpoints using only centers .
Schedule those two queries at different processing times: after processing and after processing . Then update with . Store predecessors for reconstruction, and print the chosen creepers sorted by explosive power.
Turn explosions into intervals
For a creeper with , detonating it kills
Creepers with are never usable as detonations.
If this were just “cover with the fewest intervals”, life would be easy. But there is one annoying rule: a creeper must still be alive when we detonate it. So if we choose two creepers that can kill each other, whichever one we detonate first deletes the other one. That pair cannot both be used. Very rude, very Codeforces.
What a valid chosen set looks like
Take the chosen creepers and sort them by position:
We can assume every chosen interval extends the covered prefix. If a chosen creeper does not extend the farthest killed position to the right, removing it still covers all positions and cannot make the sequence less valid. So in an optimal solution, the prefix endpoints strictly increase.
Now consider two consecutive chosen creepers in this position order. To cover without a gap, we need
Also, the new interval must extend farther:
The pair is valid if they do not kill each other. Since , there are two safe cases:
At least one of these must hold, otherwise both centers lie inside each other's blast radius, and using both is impossible.
The DP state
Use a segment tree over prefix endpoints .
At position , store the best pair
among ways to kill exactly the prefix using only creepers processed so far.
Base state:
meaning we have killed nothing and chosen no last creeper.
For each usable creeper , we want the best predecessor chain that can come before it.
Let
There are two transition types.
Case 1: previous blast stops before
We need
This query should be made after all possible previous centers have been processed. So we schedule it at time :
Case 2: previous center is left of 's blast
We need . Instead of storing in the query, we enforce this by timing: make this query right after processing center . Then the segment tree contains only chains whose last center is at most .
The previous endpoint must connect to 's interval and must be before :
So we schedule
at time .
The minimum of these scheduled query answers is , the best number of detonations before adding creeper . Then selecting gives detonations and covers prefix , so we update
We also store the predecessor creeper from the segment tree answer, so reconstruction is just following parent pointers.
Why this does not miss weird non-adjacent conflicts
The DP explicitly prevents consecutive chosen creepers from killing each other mutually.
Could a non-consecutive pair still mutually kill each other? Suppose earlier chosen creeper and later chosen creeper mutually kill each other. Let be the immediate predecessor of in position order. Since endpoints strictly increase, . Because kills , we have , so too, meaning also kills .
And since kills , and , also kills .
So and would be a mutually killing consecutive pair, contradiction. Nice little trapdoor proof.
Getting a valid detonation order
After reconstructing the chosen creepers, sort them by increasing explosive power .
Why does that work? If an earlier printed creeper killed a later printed creeper , then
But because we sorted by power, , so also
meaning kills too. That would be a mutual-kill pair, which we already proved cannot exist. Therefore every printed creeper is alive when detonated.
Complexity
Each creeper creates two range-min queries and at most one point update. Everything is segment tree work, so the complexity is
per test case, with memory.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const int INF = 1e9;
struct SegTree {
int n;
vector<pair<int, int>> tree;
SegTree(int sz = 0) { init(sz); }
void init(int sz) {
n = 1;
while (n < sz) n <<= 1;
tree.assign(2 * n, {INF, -1});
}
void update(int pos, pair<int, int> val) {
pos += n;
if (!(val < tree[pos])) return;
tree[pos] = val;
for (pos >>= 1; pos; pos >>= 1) {
tree[pos] = min(tree[pos << 1], tree[pos << 1 | 1]);
}
}
pair<int, int> query(int l, int r) const {
if (l > r) return {INF, -1};
l += n;
r += n;
pair<int, int> res = {INF, -1};
while (l <= r) {
if (l & 1) res = min(res, tree[l++]);
if (!(r & 1)) res = min(res, tree[r--]);
l >>= 1;
r >>= 1;
}
return res;
}
};
struct Query {
int l, r, id;
};
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> e(n + 1);
for (int i = 1; i <= n; i++) cin >> e[i];
vector<vector<Query>> scheduled(n + 1);
for (int i = 1; i <= n; i++) {
if (e[i] == 0) continue;
int leftMinusOne = max(0, i - e[i]);
int right = min(n, i + e[i] - 1);
scheduled[i - 1].push_back({leftMinusOne, i - 1, i});
scheduled[leftMinusOne].push_back({leftMinusOne, right - 1, i});
}
vector<pair<int, int>> bestPrev(n + 1, {INF, -1});
SegTree seg(n + 1);
seg.update(0, {0, 0});
for (int time = 0; time <= n; time++) {
if (time > 0 && e[time] > 0 && bestPrev[time].first < INF) {
int right = min(n, time + e[time] - 1);
seg.update(right, {bestPrev[time].first + 1, time});
}
for (const Query &q : scheduled[time]) {
bestPrev[q.id] = min(bestPrev[q.id], seg.query(q.l, q.r));
}
}
auto ansState = seg.query(n, n);
if (ansState.first >= INF) {
cout << -1 << '\n';
continue;
}
vector<int> ans;
for (int cur = ansState.second; cur != 0; cur = bestPrev[cur].second) {
ans.push_back(cur);
}
sort(ans.begin(), ans.end(), [&](int a, int b) {
if (e[a] != e[b]) return e[a] < e[b];
return a < b;
});
cout << ans.size() << '\n';
for (int x : ans) cout << x << ' ';
cout << '\n';
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const int INF = 1e9;
struct SegTree {
int n;
vector<pair<int, int>> tree;
SegTree(int sz = 0) { init(sz); }
void init(int sz) {
n = 1;
while (n < sz) n <<= 1;
tree.assign(2 * n, {INF, -1});
}
void update(int pos, pair<int, int> val) {
pos += n;
if (!(val < tree[pos])) return;
tree[pos] = val;
for (pos >>= 1; pos; pos >>= 1) {
tree[pos] = min(tree[pos << 1], tree[pos << 1 | 1]);
}
}
pair<int, int> query(int l, int r) const {
if (l > r) return {INF, -1};
l += n;
r += n;
pair<int, int> res = {INF, -1};
while (l <= r) {
if (l & 1) res = min(res, tree[l++]);
if (!(r & 1)) res = min(res, tree[r--]);
l >>= 1;
r >>= 1;
}
return res;
}
};
struct Query {
int l, r, id;
};
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> e(n + 1);
for (int i = 1; i <= n; i++) cin >> e[i];
vector<vector<Query>> scheduled(n + 1);
for (int i = 1; i <= n; i++) {
if (e[i] == 0) continue;
int leftMinusOne = max(0, i - e[i]);
int right = min(n, i + e[i] - 1);
scheduled[i - 1].push_back({leftMinusOne, i - 1, i});
scheduled[leftMinusOne].push_back({leftMinusOne, right - 1, i});
}
vector<pair<int, int>> bestPrev(n + 1, {INF, -1});
SegTree seg(n + 1);
seg.update(0, {0, 0});
for (int time = 0; time <= n; time++) {
if (time > 0 && e[time] > 0 && bestPrev[time].first < INF) {
int right = min(n, time + e[time] - 1);
seg.update(right, {bestPrev[time].first + 1, time});
}
for (const Query &q : scheduled[time]) {
bestPrev[q.id] = min(bestPrev[q.id], seg.query(q.l, q.r));
}
}
auto ansState = seg.query(n, n);
if (ansState.first >= INF) {
cout << -1 << '\n';
continue;
}
vector<int> ans;
for (int cur = ansState.second; cur != 0; cur = bestPrev[cur].second) {
ans.push_back(cur);
}
sort(ans.begin(), ans.end(), [&](int a, int b) {
if (e[a] != e[b]) return e[a] < e[b];
return a < b;
});
cout << ans.size() << '\n';
for (int x : ans) cout << x << ' ';
cout << '\n';
}
}