Card Game II (AOJ 0504)
もう3月も前のことですが,AOJのVol. 5を埋め終わりました.この問題の解説はあまり見ないので,その記念に書こうと思います.競プロを始めた頃はややこしい数学ゲーという印象がありましたが,意外とすっと解けます.
解法
ゲームで引く順番にカードを並べると数列が1つ得られます.ちょうど 回の再開で成功したとき,この数列がどうなっているかを考えます.
のとき
最初に山 のカードを引いた後,山 からカードを引くためには数 が書かれたカードを引く必要があります.そのため,山 のカードをすべて引くにはそれより1枚少ない枚数の の書かれたカードを引く必要があり,山 のカードをすべて引くにはそれと同じ枚数の の書かれたカードを引く必要があります.その後,その山でゲームを終了するには同じカードをさらに1枚引く必要があります.ゲーム開始時,数 が書かれたカードは山 のカードと同じ枚数しかなかったので,2番目以降の山でゲームを終了するにはカードが足りません.したがって,最後に引いたカードには が書かれています.
のとき
同様の考察から失敗する直前に引いたカードには が書かれていることが分かります.これは山 からカードを引こうとして失敗したことを意味するので,山 のカードと の書かれたカードは失敗するまでにすべて引かれています.山 のカードと数 が書かれたカードについては引いた枚数が同じなので,残る枚数も同じです.特に再開後最初にカードを引いた山の番号を とすると,山 のカードはすべて引かれているので, の書かれたカードもすべて引かれています.ゲーム再開時の山 のカードと数 が書かれたカードの残り枚数は同じなので,最初と同じ考察ができて,ゲームの最後に引いたカードには が書かれていることが分かります.
以上により,ゲームで得られる数列は
- のとき:最後は で終わる(条件1)
- のとき: 以上 以下の整数 が存在し, 未満の数の中で最も後ろにあるのは で,最後は で終わる(条件2)
を満たすことが分かりました.
逆にこれらの条件を満たす数列からその数列が得られるちょうど 回の再開で成功する配置を作ることができます.例えば,条件1を満たす数列が与えられたとき,1番目の数が書かれたカードを山 に置き,2番目以降の数が書かれたカードをその直前の数の山に置くことで,再開なしで成功する配置が得られます.この対応は1対1対応なので,条件1,2を満たす数列を数えればよくなります.
条件1を満たす数列の個数 は
です.各 に対して,条件2を満たす数列の個数 は
です.これは 未満の数をまとめて扱い,その後で分けて扱うことで得られます.可能な配置は 個あります.
最後に簡単な計算をすることで,求める確率が
- のとき:
- のとき:
であることが分かります.
#include <bits/stdc++.h> #define fst(t) std::get<0>(t) #define snd(t) std::get<1>(t) #define thd(t) std::get<2>(t) #define unless(p) if(!(p)) #define until(p) while(!(p)) using ll = std::int64_t; using P = std::tuple<int,int>; template <int r> struct Decimal{ std::vector<int> digits; Decimal(int n) : digits(r + 1, 0) { // 1/n int rem = 1; for(int i=1;i<=r;++i){ rem *= 10; digits[i] = rem / n; rem %= n; } } Decimal<r>& operator+=(Decimal<r> &rhs){ for(int i=r;i>0;--i){ digits[i] += rhs.digits[i]; if(digits[i] >= 10){ digits[i] -= 10; digits[i - 1] += 1; } } return *this; } }; constexpr int r = 10100; int main(){ std::cin.tie(nullptr); std::ios::sync_with_stdio(false); while(true){ int N, K, M, R; std::cin >> N >> K >> M >> R; if(N == 0){ break; } Decimal<r> p(N); if(M == 1){ for(int k=1;k<=N-1;++k){ Decimal<r> d(k * N); p += d; } } std::cout << p.digits[0] << "."; for(int i=1;i<=R;++i){ std::cout << p.digits[i]; } std::cout << std::endl; } }