Rustで動的計画法:Permutation
はじめに
動的計画法を実装してみて、Rustの勉強をやってみる。問題としてはEducational DP Contestという動的計画法の練習を目的としたコンテストのものを使用。
AからZまで問題が設定されているが、今回はTのPermutationを解いてみる。順列を文字列
利用したライブラリ
標準入力からデータを読み取る部分はproconio
を利用。
総当たりでの実装
まずは総当たりでやってみる。深さ優先探索で順列remain
から条件にあう数値を取り出しつつ、進めていく。
use proconio::input;
use proconio::marker::Chars;
const MOD:usize = 10usize.pow(9) + 7;
fn check(s: char, a: usize, b: usize) -> bool {
if s == '>' { a > b } else { a < b }
}
fn solve(remain: &[usize], str: &[char], str_pos: usize, priv_value: usize) -> usize {
let mut count = 0;
for (index, value) in remain.iter().enumerate() {
if check(str[str_pos].clone(), priv_value, *value) {
if remain.len() > 1 {
let mut r = remain.to_vec();
r.swap_remove(index);
count = (count + solve(&r, str, str_pos + 1, *value)) % MOD;
} else {
count = (count + 1) % MOD;
}
}
}
count
}
fn main(){
input! {
n: usize,
s: Chars,
}
let array = Vec::from_iter(0..n);
let mut ans = 0;
for i in 0..n {
let mut remain = array.clone();
remain.swap_remove(i);
ans = (ans + solve(&remain, &s, 0, i)) % MOD;
}
println!("{}", ans);
}
ベクタと行列とスライス
RustではベクタVec<T>
と行列[T; N]
がある。ベクタは可変長だが、行列はコンパイル時に長さが決まっていないといけない固定長。「コンパイル時に長さが決まっていないといけない」というのがネックで使い所が難しい気がする。
これとは別にスライス&[T]
というものがありこちらは、データコレクションの一部の要素を示す。関数の引数などに使われることが多いイメージだがが、これが分かりにくい...というか分かったつもりでいても実際使うとエラーがでることが多い。
今回は、関数内で変更しない(immutable)ベクタを渡す場合は、スライスで渡すとベクタでも行列でも関数を呼び出すことができるという観点で使っている。つまりここは
fn solve(remain: &Vec<usize>, str: &Vec<char>, str_pos: usize, priv_value: usize) -> usize {
と、ベクタの参照を渡してもいいし、remain
は関数呼び出しのために作っているので
fn solve(remain: Vec<usize>, str: &Vec<char>, str_pos: usize, priv_value: usize) -> usize {
と、所有権ごと渡しても構わない。ただ、ベクタを関数の引数にしてしまうとコピーが発生しそうに見えるので参照渡し、さらには好みかもしれないが、スライス渡しの方が見通しはいいのかもしれない。とはいいつつ🐍LCSではベクタの参照渡しで書いているが。
スライスはclone
できないので、スライスからベクタを作りたいときはto_vec()
を使う。ここがちょっと直感的な書き方でないのが少し気になる。
let mut r = remain.to_vec();
removeとswap_remove
ベクタから特定の位置の要素を削除するときはremove
を使う。
r.remove(index);
ただ、このときindex
より後ろの要素を前にずらす必要があるので処理としてはswap_remove
を使うと良い。削除したindex
の位置に、ベクタの最後尾の要素を持ってくるので、
2つのベクタを渡す
上のコードを見ると、remain
の中から条件を見たす要素を見つけて、それをpriv_value
とし、次の条件、つまりpriv_value
より大きいのか小さいのかに使っている。ただし、数列は降順にならんでいるため、priv_value
を渡す代わりに、それよりも大きい残りの要素、小さい残りの要素を渡す事は容易である。そこで、以下のように修正する。
use proconio::input;
use proconio::marker::Chars;
const MOD:usize = 10usize.pow(9) + 7;
fn solve(left: &[usize], right: &[usize], str: &[char], str_pos: usize) -> usize {
if str_pos == str.len() {
1
} else {
let mut count = 0;
if str[str_pos] == '>' {
for i in 0..left.len() {
let mut l = left.to_vec();
let mut r = right.to_vec();
let mut c = l.split_off(i + 1);
l.pop();
c.append(&mut r);
count = (count + solve(&l, &c, str, str_pos + 1)) % MOD;
}
} else {
for i in 0..right.len() {
let mut l = left.to_vec();
let mut c = right.to_vec();
let r = c.split_off(i + 1);
c.pop();
l.append(&mut c);
count = (count + solve(&l, &r, str, str_pos + 1)) % MOD;
}
}
count
}
}
fn main(){
input! {
n: usize,
s: Chars,
}
let array = Vec::from_iter(0..n);
let mut ans = 0;
for i in 1..n {
let mut left = array.clone();
let right = left.split_off(i);
left.pop();
ans = (ans + solve(&left, &right, &s, 0)) % MOD;
}
println!("{}", ans);
}
ベクタの分割と結合
ベクタを分割するにはsplit_off()
を使う。i
番目以上の要素を切り出してベクタにして返す。
let mut a = vec![1, 2, 3, 4, 5];
let b = a.split_off(3);
println!("{:?} {:?}", a, b);
// [1, 2, 3] [4, 5]
結合するときはappend
を使う。append元のベクタの中身は空っぽになる。
let mut a = vec![1, 2, 3];
let mut b = vec![4, 5];
a.append(&mut b);
println!("{:?} {:?}", a, b);
// [1, 2, 3, 4, 5] []
前の数値より小さい数値の数で考える
permutation_2.rs
をよく見ると、right
やleft
の中に何が入っているかはあまり関係なく、前に設置した要素より小さい要素が何個残っているのか、大きい要素が何個残っているかだけしか見ていない。つまり、それぞれの行列の要素数だけで処理ができる。具体的にsolve()
を書き直すと以下のようになる。
fn solve(left: usize, right: usize, str: &[char], str_pos: usize) -> usize {
if str_pos == str.len() {
1
} else {
let mut count = 0;
if str[str_pos] == '>' {
for i in 0..left {
count = (count + solve(left - i - 1, right + i, str, str_pos + 1)) % MOD;
}
} else {
for i in 0..right {
count = (count + solve(left + i, right - i - 1, str, str_pos + 1)) % MOD;
}
}
count
}
}
動的計画法で解く
これまでは、総当たりで解いており、
🍬Candiesでやったように累積和を使うことを忘れなければ容易に実装できる。計算量としては
use proconio::input;
use proconio::marker::Chars;
const MOD:usize = 10usize.pow(9) + 7;
fn main(){
input! {
n: usize,
s: Chars,
}
let mut dp = vec![1usize; n];
for c in &s {
let l = dp.len() ;
let mut next_dp = vec![0usize; l - 1];
if *c == '>' {
for j in 1..l {
next_dp[0] = (next_dp[0] + dp[j]) % MOD;
if j < l - 1 {
next_dp[j] = (next_dp[j] + MOD - dp[j]) % MOD;
}
}
} else {
for j in 0..(l - 1) {
next_dp[j] = (next_dp[j] + dp[j]) % MOD;
}
}
for j in 1..(l - 1) {
next_dp[j] = (next_dp[j] + next_dp[j - 1]) % MOD;
}
dp = next_dp;
}
let ans = dp[0];
println!("{}", ans);
}
過去の記事
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