Rustで動的計画法:Subtree
はじめに
動的計画法を実装してみて、Rustの勉強をやってみる。問題としてはEducational DP Contestという動的計画法の練習を目的としたコンテストのものを使用。
AからZまで問題が設定されているが、今回はVのSubtreeを解いてみる。rootが同じ部分木を求める問題。すべてのノードをrootとした時の答えをださないといけないので、計算量が増える可能性があるのが課題である。
利用したライブラリ
標準入力からデータを読み取る部分はproconio
を利用。
ベクタから条件にあう要素の位置を得るためにitertools
に入っているpositions
を使う。
完成したコード
長いので、フルサイズはgistに置いている。
アルゴリズム
「黒い頂点のみを辿って到達できる」組み合わせの数と部分木の数は等しいので部分木を求める問題である。ここで、
言い方を変えると、
この時、
つまり、すべての
下から上へ
let mut queue: Vec<_> = nodes.iter()
.positions(|node| node.neighbors.len() <= 1)
.collect();
let root = 'bottom_up_loop: loop {
let mut next_queue = Vec::new();
for &i in queue.iter() {
if let Some(parent) = nodes[i].set_parent_node() {
let value = nodes[i].get_value(parent);
if nodes[parent].set_child_value(i, value) == 1 {
next_queue.push(parent);
}
} else {
break 'bottom_up_loop i;
}
}
queue = next_queue;
assert!(queue.len() > 0);
};
リーフノードからルートノードに向かって計算する方法は、Indipendent Setと同じような形。まず、隣接ノードが1つしかないリーフノードをqueue
に入れてループをスタートさせる。
nodes[i].set_child_value(j, value)
で保存して進んでいく。その時nodes[i]
の隣接ノードからj
を削除する。i
の隣接ノードのうち、p
以外のnext_queue
に挿入される。
最後にはルートノードの番号を返してroot
に代入している。
上から下へ
let mut queue = vec![root];
'top_down_loop: loop {
let mut next_queue = Vec::new();
for &i in &queue {
for parent in nodes[i].value.clone() {
let p = parent.0;
if nodes[p].neighbors.len() > 0 {
next_queue.push(p);
}
let value = nodes[i].get_value(p);
let j = nodes[p].set_child_value(i, value);
assert!(j == 0);
}
}
if next_queue.len() == 0 {
break 'top_down_loop;
}
queue = next_queue;
};
上から下の流れとしては、queue
に入れ、隣接ノードに対して
Immutable borrow
ちょっと無駄のように感じるのがここのclone()
。
for parent in nodes[i].value.clone() {
普通にiter()
にすると以下の場所でエラーがでる。
let j = nodes[p].set_child_value(i, value);
つまり、for
でimmutable borrowしているnodes
がset_child_value()
の部分でmutable borrowになっているということ。書換不可で使っているものが途中で書き換えられちゃう(可能性がある)ためのエラーをだしている。実際はi != p
なので、そんな事は起きないが、その条件をコンパイラに渡す方法は見当たらなかった。clone()
してしまえばfor
のイテレータとnodes
は分離されるので、エラーはでなくなる。
測定結果
Indipendent Setで使ったツリー生成のスクリプトを使って、ノード数と時間の関係を計測したのが以下のグラフ。
乗算の計算部分で、同じ計算を何度もやっている部分があるので何とかした方がいいのかな?とも思ったが、これで十分な結果がでた。(余計なことをした方が遅かった...)
関連記事
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