ardririyの足跡

CC200 - MINSET

CC200 - MINSET

upsolveにあたって解説の内容をちゃんと理解したかったので文字に起こしたログ。どちらかというとメモ用に近くて、Editorialの内容そのまま(か不足がある)ぐらいのものです。

問題概要

正整数NNが与えられます。以下の条件を満たす集合S={(A1,B1)(AS,BS}S=\{(A_1, B_1) \cdots (A_{|S|}, B_{|S|}\}を「良い集合」と呼びます。

  • 1Ai,BiN(i=1,,S)1 \leq A_i, B_i \leq N (i=1, \cdots, |S|)
  • 長さNNの順列PPとしてあり得るものすべてについて、以下の操作を繰り返すことでソートされた状態にすることができる
    • i(1iS)i (1\leq i \leq |S|)を選び、PAiPBiP_{A_i}とP_{B_i}を入れ替える

良い集合について、そのスコアをi=1SAiBi\sum_{i=1}^{|S|} A_i \oplus B_iで定めます。達成可能なスコアの外界を求めてください。

Codechef MINSET

解法

まず、条件の2を満たすような集合がどのようなものかを考えると、(割とすぐに思いつくのが)Ai,BiA_i, B_iを辺として見て、NN頂点S|S|辺からなるグラフが連結であるかどうかが条件になっていそうである。実際にこれは正しい。

ある2頂点u,vu, vを繋ぐ辺のコストはuvu \oplus vで定められているので、この問題はグラフの最小全域木を求める問題に帰着できる。

ここで、KK2KN2^K \leq Nを満たす最大のKKと定義する。このとき、頂点を[1,2K1][1, 2^K - 1][2K,N][2^K, N]の2つの集合に分けると、この2集合を繋ぐ辺が1つ必要になる。このときにコストを最小とするような辺が何かを考えると以下のようになる。

  • N=2KN=2^Kの場合: 112K2^Kをつなぐ場合で、コスト2K+12^K+1
  • そうでない場合: 1と2K+12^K + 1を繋ぐ場合で、コスト2K2^K

続いて、[1,2K1][1, 2^K - 1][2K,N][2^K, N]のそれぞれの集合でMSTを構成する方法を考える。前者について1については、N=2K1N=2^K-1と置き直して上記の手法を再帰的に適用することでコストを求めることができる。後者についてはKKの取り方から、どの2点をとっても最上位のbitがxorにより0になるため、区間を[0,N2K][0, N-2^K]と置き直すことができる。

ここで、集合の最小値がゼロの場合、つまり頂点番号00を含むR+1R+1頂点のグラフを構築する場合に、コストがどうなるかを考える。基本的には同じように考えれば良くて、2KR2^{K^\prime} \leq R以下のKK^\primeを選んで分割をし、その2つの集合をコスト最小で繋ぐ辺を追加していけば良い。このコストは、頂点00と頂点2K2^{K^\prime}を選択することでコスト2K2^{K^\prime}で繋ぐことができ、また分割の仕方を考えるとこの繋ぎ方が最適である。分割後の集合は[0,2K1][0,2^{K^\prime}-1], [2K,R][2^{K\prime}, R]となり、後者は先程同様の議論で[0,R2K][0, R-2^{K^\prime}]と同一視できる。よって、同じ手法を再帰的に適用することで全体を繋ぐコストを求めることができる。


ここまでの議論から、

集合{1,2,,x}\{1, 2, \cdots, x\}に対してMSTを構築するときのコストをf1(x)f_1(x) 集合{0,1,2,,x}\{0,1,2,\cdots, x\}に対してMSTを構築するときのコストをf0(x)f_0(x)

と定義すると、以下のように計算することができる。ただし、KK2Kx2^K \leq xを満たす最大のKKである。

  • f0(x)=2K+f0(2K1)+f0(x2k)f_0(x) = 2^K + f_0(2^K-1) + f_0(x-2^k)
  • f1(x)=2K+[x=2K]+f1(2K1)+f0(x2K)f_1(x) = 2^K + [x=2^K] + f_1(2^K-1) + f_0(x-2^K)

ただし、[x=2K][x=2^K]1 if x=2K, otherwise 01 \text{ if } x=2^K, \text{ otherwise } 0を表す2。また、f0(1)=0,f1(1)=0f_0(1) = 0, f_1(1) = 0である。

求めるべきはf1(N)f_1(N)である。これはそのまま実装するとO(N)O(N)となるが、適切にメモ化することによってO(logN)O(\log N)で求めることができる。提出

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);
    
}

ムズいだろ!

Footnotes

  1. 分割してできた区間の左側

  2. ここの記法気持ち悪すぎて色々試行錯誤したけど、数式内に直接if - otherwiseを書くのも微妙で結局あきらめて公式解説のものを流用してきた