セグ木上で DP する問題として、人生で最初に解くべき問題と言えるかもしれない!
問題概要
個の区間がある。各区間 は、 で重みは である。
これらの区間からいくつか選ぶ方法のうち、 全体を被覆するものについて、最小重みを求めよ。
制約
考えたこと
区間がいっぱいある問題では、とりあえず左端か右端でソートするとうまくいくことが多い。そして、次の配列を考えよう。
dp[i]
← を被覆するコストの最小値
今回は区間を右端でソートしておいて、その順に処理していく。その区間が で重みが であるとする。このとき DP 更新式は
chmin(dp[r], min(dp[l], dp[l+1], ..., dp[r-1]) + c)
と表現できる。これは配列 dp を RMQ で表現することで高速に更新できる。全体の計算量は となる。
コード
#include <bits/stdc++.h> using namespace std; #define COUT(x) cout << #x << " = " << (x) << " (L" << __LINE__ << ")" << endl // Segment Tree template<class Monoid> struct SegmentTree { using Func = function<Monoid(Monoid, Monoid)>; // core member int N; Func OP; Monoid IDENTITY; // inner data int log, offset; vector<Monoid> dat; // constructor SegmentTree() {} SegmentTree(int n, const Func &op, const Monoid &identity) { init(n, op, identity); } SegmentTree(const vector<Monoid> &v, const Func &op, const Monoid &identity) { init(v, op, identity); } void init(int n, const Func &op, const Monoid &identity) { N = n; OP = op; IDENTITY = identity; log = 0, offset = 1; while (offset < N) ++log, offset <<= 1; dat.assign(offset * 2, IDENTITY); } void init(const vector<Monoid> &v, const Func &op, const Monoid &identity) { init((int)v.size(), op, identity); build(v); } void pull(int k) { dat[k] = OP(dat[k * 2], dat[k * 2 + 1]); } void build(const vector<Monoid> &v) { assert(N == (int)v.size()); for (int i = 0; i < N; ++i) dat[i + offset] = v[i]; for (int k = offset - 1; k > 0; --k) pull(k); } int size() const { return N; } Monoid operator [] (int i) const { return dat[i + offset]; } // update A[i], i is 0-indexed, O(log N) void set(int i, const Monoid &v) { assert(0 <= i && i < N); int k = i + offset; dat[k] = v; while (k >>= 1) pull(k); } // get [l, r), l and r are 0-indexed, O(log N) Monoid prod(int l, int r) { assert(0 <= l && l <= r && r <= N); Monoid val_left = IDENTITY, val_right = IDENTITY; l += offset, r += offset; for (; l < r; l >>= 1, r >>= 1) { if (l & 1) val_left = OP(val_left, dat[l++]); if (r & 1) val_right = OP(dat[--r], val_right); } return OP(val_left, val_right); } Monoid all_prod() { return dat[1]; } // get max r that f(get(l, r)) = True (0-indexed), O(log N) // f(IDENTITY) need to be True int max_right(const function<bool(Monoid)> f, int l = 0) { if (l == N) return N; l += offset; Monoid sum = IDENTITY; do { while (l % 2 == 0) l >>= 1; if (!f(OP(sum, dat[l]))) { while (l < offset) { l = l * 2; if (f(OP(sum, dat[l]))) { sum = OP(sum, dat[l]); ++l; } } return l - offset; } sum = OP(sum, dat[l]); ++l; } while ((l & -l) != l); // stop if l = 2^e return N; } // get min l that f(get(l, r)) = True (0-indexed), O(log N) // f(IDENTITY) need to be True int min_left(const function<bool(Monoid)> f, int r = -1) { if (r == 0) return 0; if (r == -1) r = N; r += offset; Monoid sum = IDENTITY; do { --r; while (r > 1 && (r % 2)) r >>= 1; if (!f(OP(dat[r], sum))) { while (r < offset) { r = r * 2 + 1; if (f(OP(dat[r], sum))) { sum = OP(dat[r], sum); --r; } } return r + 1 - offset; } sum = OP(dat[r], sum); } while ((r & -r) != r); return 0; } // debug friend ostream& operator << (ostream &s, const SegmentTree &seg) { for (int i = 0; i < (int)seg.size(); ++i) { s << seg[i]; if (i != (int)seg.size() - 1) s << " "; } return s; } }; const long long INF = 1LL<<60; int main() { int N, L; cin >> N >> L; vector<long long> l(N), r(N), c(N); for (int i = 0; i < N; ++i) { cin >> l[i] >> r[i] >> c[i]; } vector<int> ids(N); for (int i = 0; i < N; ++i) ids[i] = i; sort(ids.begin(), ids.end(), [&](int i, int j){return r[i] < r[j];}); SegmentTree<long long> dp(L+1, [&](long long a, long long b){return min(a,b);}, INF); dp.set(0, 0); for (auto i : ids) { // dp[l[i]], dp[l[i]+1], ..., dp[r[i]-1] の最小値をとる long long miv = dp.prod(l[i], r[i]); // dp[r[i]] にセット dp.set(r[i], min(dp.prod(r[i], r[i]+1), miv + c[i])); } cout << dp.prod(L, L+1) << endl; }