D - Dance Editorial by leaf1415
\(2N\) 人の参加者が \(N\) 個のペアに分かれる方法(以下、単に「分け方」と呼ぶ)をすべて全探索し、その中での舞踏会全体の楽しさの最大値を出力すれば良いです。
以下では、分け方の個数が最大となる \(N = 8\) の場合を例に説明します。
分け方を \(1\) つ選ぶ自然な方法として、次のような手順を考えます。
- \(1\) 個目のペアの \(1\) 人目を誰にするかを、\(16\) 人の中から選ぶ。
- \(1\) 個目のペアの \(2\) 人目を誰にするかを、残りの \(15\) 人の中から選ぶ。
- \(2\) 個目のペアの \(1\) 人目を誰にするかを、残りの \(14\) 人の中から選ぶ。
- \(2\) 個目のペアの \(2\) 人目を誰にするかを、残りの \(13\) 人の中から選ぶ。
- \(3\) 個目のペアの \(1\) 人目を誰にするかを、残りの \(12\) 人の中から選ぶ。
- \(3\) 個目のペアの \(2\) 人目を誰にするかを、残りの \(11\) 人の中から選ぶ。
- \(\cdots\)
- \(8\) 個目のペアの \(1\) 人目を誰にするかを、残りの \(2\) 人の中から選ぶ。
- \(8\) 個目のペアの \(2\) 人目を誰にするかを、残りの \(1\) 人の中から選ぶ。
この方法ですべての分け方を全探索すると、\(16 \times 15 \times 14 \times \cdots \times 2 \times 1 > 2 \times 10^{13}\) 通りの選び方を探索することになり、実行制限時間に間に合わせることはできません。
上記の探索方法では、同じ分け方を複数回探索することになり、冗長です。
例えば、\(i\) 個目のペアの \(1 \)人目を \(x_{i, 1}\) 、\(2\) 人目を \(x_{i, 2}\) で表すと、上記の探索方法では次の \(3\) つの選び方はそれぞれ異なる選び方として調べられますが、これらは実際には本質的に同じ分け方です。
\(\big((x_{1, 1}, x_{1, 2}), (x_{2, 1}, x_{2, 2}), \ldots, (x_{8, 1}, x_{8, 2})\big) = \big((1, 2), (3, 4), \ldots, (15, 16)\big)\)
\(\big((x_{1, 1}, x_{1, 2}), (x_{2, 1}, x_{2, 2}), \ldots, (x_{8, 1}, x_{8, 2})\big) = \big((2, 1), (4, 3), \ldots, (16, 15)\big)\)
\(\big((x_{1, 1}, x_{1, 2}), (x_{2, 1}, x_{2, 2}), \ldots, (x_{8, 1}, x_{8, 2})\big) = \big((15, 16), (13, 14), \ldots, (1, 2)\big)\)
これへの対処として、上記の探索法を「各ペアの \(1\) 人目はその時点で残っている人の中で番号が最小の参加者とする」ように改良します。 すなわち、次のような方法を考えます。
- \(1\) 個目のペアの \(1\) 人目を、\(16\) 人の中で番号が最小の参加者(つまり人 \(1\) )とする。
- \(1\) 個目のペアの \(2\) 人目を誰にするかを、残りの \(15\) 人の中から選ぶ。
- \(2\) 個目のペアの \(1\) 人目を、残りの \(14\) 人の中で番号が最小の参加者とする。
- \(2\)個目のペアの \(2\) 人目を誰にするかを、残りの \(13\) 人の中から選ぶ。
- \(3\) 個目のペアの \(1\) 人目を、残りの \(12\) 人の中で番号が最小の参加者とする。
- \(3\) 個目のペアの \(2\) 人目を誰にするかを、残りの \(11\) 人の中から選ぶ。
- \(\cdots\)
- \(8\) 個目のペアの \(1\) 人目を、残りの \(2\) 人の中で番号が最小の参加者とする。
- \(8\) 個目のペアの \(2\) 人目を誰にするかを、残りの \(1\) 人の中から選ぶ。
この方法ですべての分け方を全探索すると、各ペアの \(1\) 人目は随時 \(1\) 通りに定まるため、\(15 \times 13 \times 11 \times 9 \times 7 \times 5 \times 3 \times 1 =2027025\) 通りの選び方を探索すればよく、実行制限時間に間に合わせることができます。
C++言語による正解例を以下に記載します。
#include <iostream>
#include <vector>
#include <utility>
using namespace std;
int n;
int a[20][20];
vector<pair<int, int>> vec;
bool used[20];
int calc()
{
if(vec.size() == n){
int ret = 0;
for(auto p : vec) ret ^= (a[p.first][p.second]);
return ret;
}
int l;
for(int i = 1; i <= 2*n; i++){
if(!used[i]){
l = i;
break;
}
}
used[l] = true;
int ret = 0;
for(int i = 1; i <= 2*n; i++){
if(!used[i]){
vec.push_back({l, i}), used[i] = true;
ret = max(ret, calc());
vec.pop_back(), used[i] = false;
}
}
used[l] = false;
return ret;
}
int main(void)
{
cin >> n;
for(int i = 1; i <= 2*n-1; i++){
for(int j = i+1; j <= 2*n; j++){
cin >> a[i][j];
}
}
cout << calc() << endl;
return 0;
}
posted:
last update: