Rustで動的計画法:Flowers
はじめに
動的計画法を実装してみて、Rustの勉強をやってみる。問題としてはEducational DP Contestという動的計画法の練習を目的としたコンテストのものを使用。
AからZまで問題が設定されているが、今回はQのFlowersを解いてみる。
利用したライブラリ
標準入力からデータを読み取る部分はproconio
を利用。
素直に解いてみたコード
まずは、素直にdp[i]
として解いてみる。分かりやすいが、計算量は
use proconio::input;
use std::cmp::max;
fn main(){
input! {
n: usize,
h: [usize; n],
odia: [usize; n],
}
let mut dp = vec![0; n];
for i in 0..n {
dp[i] = a[i];
for j in 0..i {
if h[j] < h[i] {
dp[i] = max(dp[i], dp[j] + a[i]);
}
}
}
let ans = dp.iter().max().unwrap();
println!("{}", ans);
}
別の解き方
考え方
dp[i][h]
を「dp[i+1][h] = dp[i][h]
だが、dp[i+1][g]
は、dp[i][h]
dp[i]
からdp[i+1]
で変化するのは1箇所なので、基本的に同じ行列を使い回せば良い。
ここで後ろの「とある区間で最大値」を求めるのに適しているデータ構造としてSegment Tree, BIT (Bit Intexed Tree)を使うことで、計算量は
BITの実装
まずは、BITを実装する。BIT
という名前だとビット列を思い浮かべるので(間違ってはいないのだが)、 別名である fenwick tree からfenwick_tree
という名前で実装した。
fenwick_treeはCrateにも入っているが、区間和に特化しているので今回は使えないのと、
C++のtemplateのようなGenericの練習のために実装してみる。
pub struct FenwickTree<T, F>
{
table: Vec::<T>,
op: F,
default: T
}
impl<T,F> FenwickTree<T,F>
where
T: Clone + Copy,
F: Fn(T, T) -> T,
{
pub fn new(n: usize, operation: F, default: T) -> Self {
FenwickTree { table: vec![default; n + 1], op: operation, default: default}
}
pub fn set(&mut self, value: T, mut pos: usize) {
assert!(pos < self.table.len());
while pos < self.table.len() {
self.table[pos] = (self.op)(self.table[pos], value);
pos += pos & pos.wrapping_neg();
}
}
pub fn get(&self, mut pos: usize) -> T {
assert!(pos < self.table.len());
let mut res = self.default;
while pos != 0 {
res = (self.op)(res, self.table[pos]);
pos -= pos & pos.wrapping_neg();
}
res
}
}
Generic
GenericはC++のテンプレートみたいなものだが制約も書ける。ここではT
は保持する値の方で、F
は処理する関数である。どんな制約を入れるべきかは、分かりやすいRustのエラーに従えばいい。
impl<T,F> FenwickTree<T,F>
where
T: Clone + Copy,
F: Fn(T, T) -> T,
{
ちょっと変わっているのが関数の呼び出しで、こんな風にかっこでくくらないといけない。これもコンパイラが教えてくれるが。エラーだけでなく「どうすればいいか」を教えてくれるコンパイラは学習にはいいと思う。
self.table[pos] = (self.op)(self.table[pos], value);
他ファイルの関数の呼び出し
一番シンプルなのはmain.rs
と同じ階層にファイルを置いて、
mod fenwick_tree;
と書けばいい。呼び出し方は以下のようにする。max
は std::cmp::max
なので関数。sum
にすると区間の和になる。
let mut dp = fenwick_tree::FenwickTree::new(n, max, 0);
ソースコード
コードとしてはとてもシンプル。
dp
の初期値が0なのと、dp[i][h]
use proconio::input;
use std::cmp::max;
mod fenwick_tree;
fn main(){
input! {
n: usize,
h: [usize; n],
a: [usize; n],
}
let mut dp = fenwick_tree::FenwickTree::new(n, max, 0);
for i in 0..n {
let d: usize = dp.get(h[i]);
dp.set(d + a[i], h[i]);
}
let ans = dp.get(n);
println!("{}", ans);
}
測定結果
関連記事
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