配点: 400 点 / 実行時間制限: 2 sec / メモリ制限: 1024 MB / Difficulty: 579
問題概要
個のサイコロがあり、
番目のサイコロには
個の面があって、各面にはそれぞれ
が書かれている。このサイコロを振ると、各面がそれぞれ
の確率で出る。
個のサイコロから2個を選んで振るとき、2つのサイコロの出目が等しくなる確率の最大値を求めよ。
制約
- 入力は全て整数。
考察
確率を考える上で必要なのは次の2つ。
- ある事象
が起こる場合の数
- 全事象の場合の数
今回の場合は、
- 「
個のサイコロから2個を選んで振ったとき、同じ出目が出る」という事象の場合の数
個のサイコロから2個を選んで振ったとき、2つのサイコロの出目の組としてあり得る個数(ただし、同じ出目でも異なる面のものは区別して数える)
となる。
例えば、
個の面を持ち、数
が書かれた面が
個
個の面を持ち、数
が書かれた面が
個
という2つのサイコロを振ったとき、どちらも出目 が出る場合の数は
通り存在する。
また、この2つのサイコロの出目の組の総数は 通り存在するので、求める確率
は、
と計算できる。
それぞれの事象は排反であるから、面の種類ごとに和を取り、 の選び方をすべて試してそれを最大化すれば答えが求まる。
以下、具体的なアルゴリズムを考えていく。
まず、
サイコロ
の面の中で、数
が書かれているものの個数
という連想配列を用意する。その上で、以下のアルゴリズムを構築する。
を満たす組み合わせを二重ループで探索
- ある組み合わせ
について、
- カウンター
を
で初期化する。
の各要素
に対して、
- もし
ならば、サイコロ
に共通する出目は存在しないのでスキップ。
- そうでないとき、
に
を加算する。
- もし
を計算。
- カウンター
- それぞれの組み合わせごとに
を求めて、これを最大化する。
計算量は、 とれば、
の構築 :
の組み合わせの全探索 :
なので、全体としては となり、これは本問の制約下では十分高速。
実装においては、 計算時のオーバーフローや、除算時のキャスト処理に注意しよう(3敗)。
実装例
#include <bits/stdc++.h> using namespace std; #define fix(x) fixed << setprecision(x) #define rep(i, start, end) for (auto i = (start); (i) < (end); (i)++) using ll = long long; using lld = long double; // ======================================== // int main() { int N; cin >> N; vector<ll> K(N); vector<map<int, ll>> tables(N); rep(i, 0, N) { cin >> K[i]; rep(j, 0, K[i]) { int A; cin >> A; tables[i][A]++; } } lld ans = 0; rep(i, 0, N) rep(j, i + 1, N) { ll cnt = 0; for (auto &&table : tables[i]) { if (tables[j].contains(table.first)) { cnt += table.second * tables[j][table.first]; } } chmax(ans, (lld)cnt / (K[i] * K[j])); } cout << fix(10) << ans << endl; return 0; }
実装時間: 20分
コメント
確率とか期待値の問題が出ると身構えてしまうが、今回は計算量の改善をあまり意識せずに素直な実装をすることができた。