#include <iostream>
#include <vector>
#include <algorithm> // std::max()
#include <limits> // std::numeric_limits<>::lowest()
// セグメント木による Range Maximum Query
class SegmentTree
{
public:
SegmentTree() = default;
// ツリーの初期化(単位元は std::numeric_limits<long long>::lowest())
explicit SegmentTree(size_t size)
{
// size 以上である 2 のべき乗数を求めて m_size に代入する
while (m_size < size)
{
m_size *= 2;
}
m_nodes.resize((m_size * 2 - 1), std::numeric_limits<long long>::lowest());
}
// 値の初期化
void init(const std::vector<long long>& values)
{
for (size_t i = 0; i < values.size(); ++i)
{
m_nodes[m_size - 1 + i] = values[i];
}
for (int i = static_cast<int>(m_size) - 2; i >= 0; --i)
{
update(i);
}
}
// 一点更新
void set(size_t i, long long value)
{
// ノードのインデックス
size_t ni = (i + m_size - 1);
m_nodes[ni] = value;
// 親を更新していく
while (0 < ni)
{
// ni を親のインデックスにする
ni = (ni - 1) / 2;
// 親を更新
update(ni);
}
}
// 区間クエリ(対象区間 [begin, end) における最大値を求める)
long long query(size_t begin, size_t end) const
{
return query(begin, end, 0, 0, m_size);
}
private:
// ノードの更新
void update(size_t ni)
{
m_nodes[ni] = std::max(m_nodes[(ni * 2 + 1)], m_nodes[(ni * 2 + 2)]);
}
// 区間クエリ(対象区間 [begin, end) における最大値を求める)
// ni: 確認するノードのインデックス. そのノードの管理区間は [nBegin, nEnd)
long long query(size_t begin, size_t end, size_t ni, size_t nBegin, size_t nEnd) const
{
// クエリ対象区間が管理区間と無関係なら
if ((nEnd <= begin) || (end <= nBegin))
{
return std::numeric_limits<long long>::lowest();
}
// 管理区間のすべてがクエリ対象区間に含まれていれば
if ((begin <= nBegin) && (nEnd <= end))
{
return m_nodes[ni];
}
// query(クエリ対象区間, 子ノード(左) のインデックス, 子ノード(左) の管理区間)
const long long m1 = query(begin, end, (ni * 2 + 1), nBegin, (nBegin + nEnd) / 2);
// query(クエリ対象区間, 子ノード(右) のインデックス, 子ノード(右) の管理区間)
const long long m2 = query(begin, end, (ni * 2 + 2), (nBegin + nEnd) / 2, nEnd);
return std::max(m1, m2);
}
size_t m_size = 1;
std::vector<long long> m_nodes;
};
// 料理を表す構造体
struct Dish
{
// 最小で L [mg] の香辛料を消費
int L;
// 最大で R [mg] の香辛料を消費
int R;
// 料理の価値
int V;
};
int main()
{
// 香辛料をちょうど W [mg] 消費, 料理が N 種類
int W, N;
std::cin >> W >> N;
// 料理の情報を入力
std::vector<Dish> dishes(N);
for (auto& dish : dishes)
{
std::cin >> dish.L >> dish.R >> dish.V;
}
// dp[今まで見た料理の個数][香辛料の合計量] = 価値合計の最大値
// 不可能な状態は -1 で表現する
std::vector<std::vector<long long>> dp(N + 1, std::vector<long long>(W + 1, -1));
dp[0][0] = 0;
std::vector<SegmentTree> rmqs(N + 1, SegmentTree(W + 1));
rmqs[0].set(0, 0);
for (int i = 1; i <= N; ++i) // i 個目の料理を見る
{
// 現在の料理を作らなかった場合の状態(直前の料理完了時点と同じ)を設定
dp[i] = dp[i - 1];
// 見ている料理
const auto& dish = dishes[i - 1];
// k: 香辛料の合計量
for (int k = 0; k <= W; ++k)
{
// 現在の料理で [香辛料の合計量] k を実現するために必要な,
// 直前の料理における [香辛料の合計量] の最小値
const int fromBegin = std::max(0, (k - dish.R));
// 現在の料理で [香辛料の合計量] k を実現するために必要な,
// 直前の料理における [香辛料の合計量] の最大値(の次の要素を指して, 範囲の終端位置を表現)
const int fromEnd = std::max(0, (k - dish.L + 1));
// [fromBegin, fromEnd) で範囲を表す. (fromBegin == fromEnd) の場合は, 実現不可能
if (fromBegin == fromEnd)
{
continue;
}
// セグメント木による区間最大値取得.
// [fromBegin, fromEnd) の中で最大の価値合計値を探す
const long long max = rmqs[i - 1].query(fromBegin, fromEnd);
// 最大の価値合計値 -1 は, 直前の状態が実現不可能ということなのでスキップ
if (max != -1)
{
// 料理を作らない場合と作る場合の価値合計値を比較して, 大きいほうを採用
dp[i][k] = std::max(dp[i][k], (max + dish.V));
}
}
// セグメント木を更新
for (int k = 0; k <= W; ++k)
{
rmqs[i].set(k, dp[i][k]);
}
}
// 解答 (dp[N][W]) を出力
std::cout << dp.back().back() << '\n';
}
自作のセグメント木ではなく、AC Library に実装されているセグメント木 atcoder::segtree
を使った解法です。
#include <iostream>
#include <vector>
#include <algorithm> // std::max()
#include <limits> // std::numeric_limits<>::lowest()
#include <atcoder/segtree.hpp> // atcoder::segtree
long long Op(long long a, long long b)
{
return std::max(a, b);
}
long long E()
{
return std::numeric_limits<long long>::lowest();
}
// セグメント木による Range Maximum Query
using SegmentTree = atcoder::segtree<long long, Op, E>;
// 料理を表す構造体
struct Dish
{
// 最小で L [mg] の香辛料を消費
int L;
// 最大で R [mg] の香辛料を消費
int R;
// 料理の価値
int V;
};
int main()
{
// 香辛料をちょうど W [mg] 消費, 料理が N 種類
int W, N;
std::cin >> W >> N;
// 料理の情報を入力
std::vector<Dish> dishes(N);
for (auto& dish : dishes)
{
std::cin >> dish.L >> dish.R >> dish.V;
}
// dp[今まで見た料理の個数][香辛料の合計量] = 価値合計の最大値
// 不可能な状態は -1 で表現する
std::vector<std::vector<long long>> dp(N + 1, std::vector<long long>(W + 1, -1));
dp[0][0] = 0;
std::vector<SegmentTree> rmqs(N + 1, SegmentTree(W + 1));
rmqs[0].set(0, 0);
for (int i = 1; i <= N; ++i) // i 個目の料理を見る
{
// 現在の料理を作らなかった場合の状態(直前の料理完了時点と同じ)を設定
dp[i] = dp[i - 1];
// 見ている料理
const auto& dish = dishes[i - 1];
// k: 香辛料の合計量
for (int k = 0; k <= W; ++k)
{
// 現在の料理で [香辛料の合計量] k を実現するために必要な,
// 直前の料理における [香辛料の合計量] の最小値
const int fromBegin = std::max(0, (k - dish.R));
// 現在の料理で [香辛料の合計量] k を実現するために必要な,
// 直前の料理における [香辛料の合計量] の最大値(の次の要素を指して, 範囲の終端位置を表現)
const int fromEnd = std::max(0, (k - dish.L + 1));
// [fromBegin, fromEnd) で範囲を表す. (fromBegin == fromEnd) の場合は, 実現不可能
if (fromBegin == fromEnd)
{
continue;
}
// セグメント木による区間最大値取得.
// [fromBegin, fromEnd) の中で最大の価値合計値を探す
const long long max = rmqs[i - 1].prod(fromBegin, fromEnd);
// 最大の価値合計値 -1 は, 直前の状態が実現不可能ということなのでスキップ
if (max != -1)
{
// 料理を作らない場合と作る場合の価値合計値を比較して, 大きいほうを採用
dp[i][k] = std::max(dp[i][k], (max + dish.V));
}
}
// セグメント木を更新
rmqs[i] = SegmentTree(dp[i]);
}
// 解答 (dp[N][W]) を出力
std::cout << dp.back().back() << '\n';
}
次のようなプログラムでも正解の出力を得られますが、各料理について dp[i][k]
を求めるのに O(W^2) の計算が必要で、TLE になります。
#include <iostream>
#include <vector>
#include <algorithm> // std::max(), std::max_element()
// 料理を表す構造体
struct Dish
{
// 最小で L [mg] の香辛料を消費
int L;
// 最大で R [mg] の香辛料を消費
int R;
// 料理の価値
int V;
};
int main()
{
// 香辛料をちょうど W [mg] 消費, 料理が N 種類
int W, N;
std::cin >> W >> N;
// 料理の情報を入力
std::vector<Dish> dishes(N);
for (auto& dish : dishes)
{
std::cin >> dish.L >> dish.R >> dish.V;
}
// dp[今まで見た料理の個数][香辛料の合計量] = 価値合計の最大値
// 不可能な状態は -1 で表現する
std::vector<std::vector<long long>> dp(N + 1, std::vector<long long>(W + 1, -1));
dp[0][0] = 0;
for (int i = 1; i <= N; ++i) // i 個目の料理を見る
{
// 現在の料理を作らなかった場合の状態(直前の料理完了時点と同じ)を設定
dp[i] = dp[i - 1];
// 見ている料理
const auto& dish = dishes[i - 1];
// k: 香辛料の合計量
for (int k = 0; k <= W; ++k)
{
// 現在の料理で [香辛料の合計量] k を実現するために必要な,
// 直前の料理における [香辛料の合計量] の最小値へのイテレータ
const auto itBegin = (dp[i - 1].begin() + std::max(0, (k - dish.R)));
// 現在の料理で [香辛料の合計量] k を実現するために必要な,
// 直前の料理における [香辛料の合計量] の最大値(への次の要素を指して, 範囲の終端位置を表現)するイテレータ
const auto itEnd = (dp[i - 1].begin() + std::max(0, (k - dish.L + 1)));
// [itBegin, itEnd) で範囲を表す. (itBegin == itEnd) の場合は, 実現不可能
if (itBegin == itEnd)
{
continue;
}
// [itBegin, itEnd) の中で最大の価値合計値を探す
const auto it = std::max_element(itBegin, itEnd);
// 最大の価値合計値 -1 は, 直前の状態が実現不可能ということなのでスキップ
if (*it != -1)
{
// 料理を作らない場合と作る場合の価値合計値を比較して, 大きいほうを採用
dp[i][k] = std::max(dp[i][k], (*it + dish.V));
}
}
}
// 解答 (dp[N][W]) を出力
std::cout << dp.back().back() << '\n';
}