Rustで動的計画法:Frog 3
はじめに
動的計画法を実装してみて、Rustの勉強をやってみる。問題としてはEducational DP Contestという動的計画法の練習を目的としたコンテストのものを使用。
AからZまで問題が設定されており、今回は最後のZ Frog 3を解いてみる。結構有名な問題らしく解法の解説は多い。
利用したライブラリ
標準入力からデータを読み取る部分はproconio
を利用。
chmax
のマクロも利用。
解き方
動的計画法
普通にやるとこうなるよね。という解き方。最初にあったFrog, Frog 2との違いは、カエルがジャンプする距離に制限がないこと。このため、内側のループも
fn main() {
input! {
n : usize, c : u64,
mut footholds : [u64; n]
}
let mut dp = vec![u64::MAX; n];
dp[0] = 0;
for j in 1..n {
for i in 0..j {
chmin!(dp[j], dp[i] + (footholds[j] - footholds[i]).pow(2) + c);
}
}
let answer = dp[n - 1];
println!("{}", answer);
}
Convex Hull Trick (CHT)
複数の直線の集合から、各
のような形で表される問題を実装方法にもよるが、
今回の問題の場合は、
であるが、変形すると以下のようにCHTが使える形式に変換できる。
use proconio::input;
use num_traits::{Zero, NumOps};
use std::collections::VecDeque;
// LF : Liner Function
pub struct LF<T> {
a: T,
b: T
}
impl<T> LF<T>
where
T: NumOps + Copy
{
pub fn new(a:T, b:T) -> Self {
Self {a, b}
}
pub fn calc(&self, x:T) -> T {
self.a * x + self.b
}
}
pub struct CHT<T> {
line : VecDeque<LF<T>>,
priv_a : T,
priv_x : T,
}
impl<T> CHT<T>
where
T: Zero + PartialOrd + NumOps + Copy
{
pub fn new() -> Self {
Self {
line: VecDeque::<LF<T>>::new(),
priv_a: T::zero(),
priv_x: T::zero()
}
}
fn check(&self, f1_index:usize, f2_index:usize, f3:&LF<T>) -> bool {
let f1 = self.line.get(f1_index).unwrap();
let f2 = self.line.get(f2_index).unwrap();
(f2.a - f1.a) * (f3.b - f2.b) >= (f2.b - f1.b) * (f3.a - f2.a)
}
pub fn add(&mut self, f:LF<T>) {
assert!(self.line.len() < 1 || self.priv_a >= f.a);
while self.line.len() >= 2 && self.check(self.line.len() - 2, self.line.len() - 1, &f) == true {
self.line.pop_back();
}
self.priv_a = f.a;
self.line.push_back(f);
}
pub fn get(&mut self, x:T) -> T {
assert!(self.line.len() < 1 || self.priv_x <= x);
while self.line.len() >= 2 && self.line.get(0).unwrap().calc(x) >= self.line.get(1).unwrap().calc(x) {
self.line.pop_front();
}
self.priv_x = x;
self.line.get(0).unwrap().calc(x)
}
}
fn main() {
input! {
n : usize, c : i64,
mut footholds : [i64; n]
}
let mut cht = CHT::<i64>::new();
let mut dp = vec![0; n];
for i in 1..n {
cht.add(LF::new(-2 * footholds[i-1], dp[i-1] + footholds[i-1].pow(2)));
dp[i] = cht.get(footholds[i]) + footholds[i].pow(2) + c;
}
let answer = dp[n - 1];
println!("{}", answer);
}
LARCH
この問題のユーザ解説記事にあったアルゴリズム。遷移コスト
以下の問題を
汎用性が高そうなアルゴリズムに感じる。良い解説はちょっと見つからなかったが、Rustでの実装も解説記事にあったのでベンチマークとしてそれをつかう。
測定結果
測定結果は以下のようになる。素直な実装では
ここらへんになるとアルゴリズムを知っているか知らないかという問題になってくると思う。
過去の記事
Rustで動的計画法の実装:
🐸Frog | 🌴Vacation | 🎒Knapsack | 🐍LCS | 🚶♂️Longest Path | 🕸️Grid | 💰Coins | 🍣Sushi | 🪨Stones | 📐dequeue | 🍬Candies | 🫥Slimes | 💑Matching | 🌲Indipendent Set | 🌻Flowers | 👣Walk | 🖥️Digit Sum | 🎰Permutation | 🐰Grouping | 🌿Subtree | ⏱️Intervals | 🗼Tower | 🐸Frog3
Discussion