LoginSignup
7
10

More than 5 years have passed since last update.

ハラハラするトーナメントの日程を求めよう

Last updated at Posted at 2016-05-23

はじめに

  • あるスポーツのトーナメント戦を8カ国で行います。4試合づつ7日間の試合を行うとします。
  • 前評判の人気順位がわかっているものとします。
  • なるべく、人気の高いペアの試合を後半にするようにして、最後までハラハラするような日程を作成してみましょう。

この問題を組合せ最適化で解いてみます。

定式化

変数を、国1、国2、日ごとに、その組合せを"選ぶ選ばない"を表す0-1変数とします。

また、変数に対応する、"2国(i,j)のある日(k)の試合の重み"を以下のようにします。この重みの和を最大化することにより、"あとの方の順位の小さい国 同士の試合"を優先するようになります。
$$重み = \frac{2^k}{iの順位 \times jの順位}$$

最大化 $\sum_i{重み_i x_i}$ 重みの総和
変数 $x_i \in \{0, 1\} ~ ~ \forall i \in 候補$ その候補を選択するかどうか
制約条件 $\sum_{i \in j,kの組~~~~~}{x_i} = 1 ~ ~ \forall j,k \in \mbox{国}$ 同じ組は1つ
$\sum_{i \in 国jを含むk日目~~~~~~~~~~~~~}{x_i} = 1 ~ ~ \forall j \in \mbox{国}, \forall k \in \mbox{日}$ 同国、同日は1つ

この問題は、スケジューリング問題の一種です。

Pythonでやってみる

まずは、組合せの表を作成します。

python3
import pandas as pd
from itertools import combinations, product
from pulp import *
ss = 'イタリア オランダ 日本 韓国 タイ ドミニカ共和国 ペルー カザフスタン'.split()
rnk = {s:(i+1) for i, s in enumerate(ss)} # 国名→順位
a = pd.DataFrame([(i, j, k, 2**k/rnk[i]/rnk[j]) for i, j in combinations(ss, 2)
                  for k in range(7)], columns='国1 国2 日 重み'.split())
a[:3]
>>>
       国1    国2      重み
0  イタリア  オランダ  0  0.5
1  イタリア  オランダ  1  1.0
2  イタリア  オランダ  2  2.0

定式化して解いてみましょう。

python3
m = LpProblem(sense=LpMaximize) # 数理モデル
a['Var'] = [LpVariable('v%d'%i, cat=LpBinary) for i in a.index] # 変数
m += lpDot(a.重み, a.Var) # 目的関数
for _, b in a.groupby(['国1', '国2']):
    m += lpSum(b.Var) == 1 # 同じ組は1つ
for s, i in product(ss, range(7)):
    # 同国、同日は1つ
    m += lpSum(a.query('(国1=="{0}" | 国2=="{0}") & 日=={1}'.format(s, i)).Var) == 1
m.solve() # 求解
a['Val'] = a.Var.apply(value) # 結果
# 表示
for i, b in a.groupby('日'):
    print(i+1, '日目 ')
    for _, r in b[b.Val > 0].iterrows():
        print(' %*s - %s'%(8-len(r.国1), r.国1, r.国2))
>>>
1 日目 
 イタリア - カザフスタン
 オランダ - ペルー
     日本 - ドミニカ共和国
     韓国 - タイ
2 日目 
 イタリア - ペルー
 オランダ - カザフスタン
     日本 - タイ
     韓国 - ドミニカ共和国
3 日目 
 イタリア - ドミニカ共和国
 オランダ - タイ
     日本 - カザフスタン
     韓国 - ペルー
4 日目 
 イタリア - タイ
 オランダ - ドミニカ共和国
     日本 - ペルー
     韓国 - カザフスタン
5 日目 
 イタリア - 韓国
 オランダ - 日本
     タイ - カザフスタン
 ドミニカ共和国 - ペルー
6 日目 
 イタリア - 日本
 オランダ - 韓国
     タイ - ペルー
 ドミニカ共和国 - カザフスタン
7 日目 
 イタリア - オランダ
     日本 - 韓国
     タイ - ドミニカ共和国
   ペルー - カザフスタン

各日の日程が出力されました。

他の方法としては、前半に戦力差のある組合せ、後半に拮抗した戦力差の組合せというのもあるかもしれません。

おまけ

一時的に、私の直近のPython関連の投稿を、Arukasで実行できるようにしています。

以上

7
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
10