AOJ1234 GIGA Universe Cup
問題リンク GIGA Universe Cup
- 概要
4つのチームによるサッカーの総当たり戦の試合結果表から、あるチームがグループ通過する確率を求めよ。
グループ通過とは1位もしくは2位になることである。
以下の用語を定義する。
勝ち点: チームが試合に勝ったら3点、引き分けなら1点、それ以外0点が試合ごとに加算される
goal difference: (ゴール回数の総和 - ゴールされた回数の総和)
順位付けのルールは以下の通り。優先順序は強い順に並んでいる。順位が同じになる場合は、1つ優先順序を下げたもので順位付けをする。
(a) 全試合の結果による勝ち点の大きい順
(b) 全試合の結果によるgoal differenceの大きい順
(c) 全試合の結果によるゴール回数の大きい順
これらを適用しても尚、同順位のチームがある場合、その同順位のチームの集合に対して以下を行う
(d) チーム集合内の試合結果による勝ち点の大きい順
(e) チーム集合内の試合結果によるgoal differenceの大きい順
(f) チーム集合内の試合結果によるゴール回数の大きい順
これでも尚同順位がある場合、チームの集合の大きさに変化がある限り(d)〜(f)による順位付けを行う。
それでも同順位がある場合、くじ引きによって順位を決める。くじ引きは公平な確率で行われる。
試合結果表には、まだ試合が行われていないので結果が載っていない個所が2つある。ただし、どのチームも既に2試合を終えているのが保証されている。未試合の試合において各チームがP点獲得する確率は問題文にある通りである。9点以上得点する確率は0である。
- 解法
全探索します。
どのチームも1試合が未試合であるので、チームが未試合の試合でP点獲得する組み合わせを全て試します。チームがP点獲得すると決めれば、試合結果表が完成し、注目チームがリーグ通過する確率が決まります。組み合わせは9^4通りなので大した数ではありません。
試合結果表から注目チームFがリーグ通過する確率をどう求めたかを説明します。
まずは、(a)(b)(c)を使って順位付けします。それにより、1位と順位がついたチームの集合をfirstとします。
firstが2チームの場合
リーグ通過するチームは決まりです。firstにFがあれば1、なければ0です。
firstが1チームの場合
firstがFならば通過確定で1です
そうでない場合、2位と順位がついているチームの集合に対して(d)(e)(f)で順位付けします。その結果
2位が1つに決まった場合
2位がFなら通過確定で1、そうでないならば敗退で0です。
それ以外の場合
Fが2位でないならば0。2位ならば、くじ引き。1/(2位となっているチーム数)でリーグ通過します
firstが3チーム以上ある場合
firstにFが入っていないならば敗退で0です。
firstに対して(d)(e)(f)で順位付けします。その結果を t とします。
1位が2チームの場合
tにFが入っていれば1, それ以外0です
1位が1チームの場合
firstからtを抜いたリストを t' とします。t'に対して(d)(e)(f)で順位付けします。その結果
2位が1チームの場合
Fが2位ならば通過確定で1、それ以外なら0です
それ以外の場合
Fが2位でないならば0。2位ならば、くじ引き。1/(2位になっているチーム数)の確率でリーグ通過します。
1位が3チーム以上ある場合
Fが1位でないならば敗退確定で0。1位ならばくじ引き。2/(1位のチーム数)の確率でリーグ通過します。
長いですが、これを頑張って実装しました。
チームがリーグ通過する確率は、(試合結果がその表になる確率 * その試合結果表でFがリーグ通過する確率)の総和です。
- ソース
import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Scanner; //GIGA Universe Cup public class AOJ1234 { //チームを表すクラス //勝ち数、Diff,ゴール数の優先順序でソートできる class R implements Comparable<R>{ //nobattleはまだ試合をしていない相手のチーム番号 int id, nobattle, point, goal, diff; boolean concerned; int[] get, lose; public R(int id) { this.id = id; get = new int[4]; lose = new int[4]; } //試合全体の結果を使って、勝ち数、Diff, ゴール数を計算 void calc1(){ point = goal = diff = 0; for(int i=0;i<4;i++){ if(id==i)continue; point += get[i]>lose[i]?3:get[i]==lose[i]?1:0; goal += get[i]; diff += get[i]-lose[i]; } } //同じ順位のチーム同士の試合結果を使って、勝ち数、Diff、ゴール数を計算 void calc2(){ if(!concerned)return; point = goal = diff = 0; for(int i=0;i<4;i++){ if(id==i||!teams[i].concerned)continue; point += get[i]>lose[i]?3:get[i]==lose[i]?1:0; goal += get[i]; diff += get[i]-lose[i]; } } public int compareTo(R o) { return point!=o.point?o.point-point:diff!=o.diff?o.diff-diff:o.goal-goal; } } //Listの中にfというチームがいるか boolean exist(int f, List<R> list){ for(R r:list)if(r.id==f)return true; return false; } //あるチームの集合の中で(d),(e),(f)のソートをし、その中で最も成績の良いチームを返す //結果は更に同順位になりうるのでリストである List<R> ranking(List<R> list){ List<R> res = new ArrayList<R>(); for(int i=0;i<4;i++)teams[i].concerned = false; R[] t = new R[list.size()]; for(int i=0;i<list.size();i++){ t[i] = list.get(i); t[i].concerned = true; } for(int i=0;i<list.size();i++)t[i].calc2(); Arrays.sort(t); res.add(t[0]); for(int i=1;i<list.size();i++)if(t[0].compareTo(t[i])==0)res.add(t[i]); return res; } //チーム4つ R[] teams; //チーム f がリーグを勝ち抜くことができる確率を返す double win(int f){ double res = 0; R[] tmp = new R[4]; //作業用の配列にコピー for(int i=0;i<4;i++)tmp[i]=teams[i]; //(a),(b),(c)によってランク付け for(int i=0;i<4;i++)tmp[i].calc1(); Arrays.sort(tmp); List<R> first = new ArrayList<R>(); first.add(tmp[0]); for(int i=1;i<4;i++)if(tmp[0].compareTo(tmp[i])==0)first.add(tmp[i]); //リスト firstには4チームの中でrank 1 (同順位含む) のチームが入っている //firstが2チームである -> リーグを勝ち抜くチームは一意に決まる if(first.size()==2){ res = exist(f, first)?1:0; } //チームが3チーム以上ある else if(first.size()>2){ //ビリなら予選落ち確定 if(!exist(f, first)){ return 0; } List<R> t = new ArrayList<R>(); for(R r:first)t.add(r); //ふるい落とせるだけ、(d)(e)(f)のランク付けをする for(;;){ int pre = t.size(); t = ranking(t); //2チームに絞れた if(t.size()==2){ res = exist(f, t)?1:0; break; } //1チームに絞れた if(t.size()==1){ //1位決定なので通過確定 if(exist(f, t))return 1; int p = t.get(0).id; //firstから1位のチームを抜いたリスト t t = new ArrayList<R>(); for(R r:first)if(p!=r.id)t.add(r); t = ranking(t); //再び(d)(e)(f)を使って絞り込む for(;;){ int size = t.size(); t = ranking(t); //1チームに絞れた こいつが2位通過 if(t.size()==1){ return res = exist(f, t)?1:0; } //絞りきれないのでくじ引き if(size==t.size()){ return res = exist(f, t)?(1.0/size):0; } } } //絞りきれないのでくじ引き else if(pre==t.size()){ return exist(f, t)?(2.0/pre):0; } } } //1位はどこかが分かった else{ //1位なら通過確定 if(exist(f, first))return 1; //firstから1位のチームを抜いたリスト t List<R> t = new ArrayList<R>(); t.add(tmp[1]); for(int i=2;i<4;i++)if(tmp[1].compareTo(tmp[i])==0)t.add(tmp[i]); //絞り込む d,e,f for(;;){ int size = t.size(); t = ranking(t); //1チームに絞れた こいつが2位通過 if(t.size()==1){ res = exist(f, t)?1:0; break; } //絞りきれないのでくじ引き if(size==t.size()){ res = exist(f, t)?(1.0/size):0; break; } } } return res; } void run(){ int[] fact = new int[9]; fact[0]=1; for(int i=1;i<=8;i++)fact[i]=fact[i-1]*i; //P点を試合で得点する確率 double[] gain = new double[9]; for(int i=0;i<=8;i++)gain[i]=fact[8]/fact[i]/fact[8-i]*Math.pow(0.25, i)*Math.pow(0.75, 8-i); Scanner sc = new Scanner(System.in); int T = sc.nextInt(); while(T--!=0){ String[] table = new String[5]; teams = new R[4]; for(int i=0;i<4;i++)teams[i]=new R(i); for(int i=0;i<5;i++)table[i]=sc.next(); int focus = 0; //行の先頭が"*"で始まっているならそのチームが注目チーム for(int i=1;i<5;i++)if(table[i].startsWith("*"))focus = i-1; for(int i=0;i<4;i++)for(int j=i+1;j<4;j++){ //得点の情報だけを抜き出す String sub = table[i+1].substring(6+5*j, 9+5*j); String[] s = sub.split("-"); //得点の数字の場所が"_"ならまだ試合がない if(s[0].equals("_")){ teams[i].nobattle = j; teams[j].nobattle = i; } else{ int x = Integer.parseInt(s[0]), y = Integer.parseInt(s[1]); teams[i].get[j] = x; teams[i].lose[j] = y; teams[j].get[i] = y; teams[j].lose[i] = x; } } double res = 0; //4チームそれぞれ0〜8点に固定する for(int a=0;a<9;a++)for(int b=0;b<9;b++)for(int c=0;c<9;c++)for(int d=0;d<9;d++){ //この状態になる確率 double p = gain[a]*gain[b]*gain[c]*gain[d]; int[] e = new int[]{a,b,c,d}; //未試合相手に対する情報を入れる for(int i=0;i<4;i++){ teams[i].get[teams[i].nobattle] = e[i]; teams[i].lose[teams[i].nobattle] = e[teams[i].nobattle]; } //リーグ通過する確率を足す res += p*win(focus); } System.out.printf("%.7f\n", res); } } public static void main(String[] args) { new AOJ1234().run(); } }