CC200 - MINSET

upsolveにあたって解説の内容をちゃんと理解したかったので文字に起こしたログ。どちらかというとメモ用に近くて、Editorialの内容そのまま(か不足がある)ぐらいのものです。
問題概要
正整数が与えられます。以下の条件を満たす集合を「良い集合」と呼びます。
- 長さの順列としてあり得るものすべてについて、以下の操作を繰り返すことでソートされた状態にすることができる
- を選び、を入れ替える
良い集合について、そのスコアをで定めます。達成可能なスコアの外界を求めてください。
解法
まず、条件の2を満たすような集合がどのようなものかを考えると、(割とすぐに思いつくのが)を辺として見て、頂点辺からなるグラフが連結であるかどうかが条件になっていそうである。実際にこれは正しい。
ある2頂点を繋ぐ辺のコストはで定められているので、この問題はグラフの最小全域木を求める問題に帰着できる。
ここで、をを満たす最大のと定義する。このとき、頂点をとの2つの集合に分けると、この2集合を繋ぐ辺が1つ必要になる。このときにコストを最小とするような辺が何かを考えると以下のようになる。
- の場合: とをつなぐ場合で、コスト
- そうでない場合: 1とを繋ぐ場合で、コスト
続いて、とのそれぞれの集合でMSTを構成する方法を考える。前者について1については、と置き直して上記の手法を再帰的に適用することでコストを求めることができる。後者についてはの取り方から、どの2点をとっても最上位のbitがxorにより0になるため、区間をと置き直すことができる。
ここで、集合の最小値がゼロの場合、つまり頂点番号を含む頂点のグラフを構築する場合に、コストがどうなるかを考える。基本的には同じように考えれば良くて、以下のを選んで分割をし、その2つの集合をコスト最小で繋ぐ辺を追加していけば良い。このコストは、頂点と頂点を選択することでコストで繋ぐことができ、また分割の仕方を考えるとこの繋ぎ方が最適である。分割後の集合は, となり、後者は先程同様の議論でと同一視できる。よって、同じ手法を再帰的に適用することで全体を繋ぐコストを求めることができる。
ここまでの議論から、
集合に対してMSTを構築するときのコストを 集合に対してMSTを構築するときのコストを
と定義すると、以下のように計算することができる。ただし、はを満たす最大のである。
ただし、はを表す2。また、である。
求めるべきはである。これはそのまま実装するととなるが、適切にメモ化することによってで求めることができる。提出
fn f0(x: usize, memo0: &mut HashMap<usize, usize>) -> usize {
if x == 0 {
return 0;
}
if x == 1 {
return 1;
}
if let Some(val) = memo0.get(&x) {
return *val;
}
let mut v = 1;
while v * 2 <= x {
v *= 2;
}
let res = v + f0(v-1, memo0) + f0(x-v, memo0);
memo0.insert(x, res);
return res;
}
fn f1(x: usize, memo0: &mut HashMap<usize, usize>, memo1: &mut HashMap<usize, usize>) -> usize {
if x <= 1 {
return 0;
}
if let Some(val) = memo1.get(&x) {
return *val;
}
let mut v = 1;
while v*2 <= x {
v *= 2;
}
let res = v + f1(v-1, memo0, memo1) + f0(x-v, memo0) + if x==v { 1 } else { 0 };
memo1.insert(x, res);
return res;
}
fn solve(ip: &mut Input) {
let n = ip.next::<usize>();
let ans = f1(n, &mut HashMap::new(), &mut HashMap::new());
println!("{}", ans);
}
ムズいだろ!