D - 円卓の席順 / Seating Order at the Round Table Editorial by admin
gemini-3-flash-thinking概要
円卓に並んだ \(N\) 個の席に \(1\) から \(N\) のスコアを割り当て、各席について「隣り合う席のうち自分よりスコアが低い席の数」が与えられた条件 \(A_i\) と一致するような順列を求める問題です。隣接関係を不等号の向き(向き付きの辺)として捉えることで、トポロジカルソートの問題に帰着させることができます。
考察
1. 隣接する席との大小関係を定義する
席 \(i\) と席 \(i+1\) のスコアの大小関係を \(d_i\) と定義します(インデックスは \(N\) を法とする)。 - \(P_i < P_{i+1}\) のとき、\(d_i = 1\) - \(P_i > P_{i+1}\) のとき、\(d_i = -1\)
このとき、各席 \(i\) において「隣の席のうちスコアが自分より小さい席」の条件は以下のように表せます。 - 席 \(i-1\) が自分より小さい \(\iff P_{i-1} < P_i \iff d_{i-1} = 1\) - 席 \(i+1\) が自分より小さい \(\iff P_{i+1} < P_i \iff d_i = -1\)
したがって、条件 \(A_i\) は以下の式のようになります。 $\(A_i = (\text{if } d_{i-1} = 1 \text{ then } 1 \text{ else } 0) + (\text{if } d_i = -1 \text{ then } 1 \text{ else } 0)\)$
2. \(A_i\) の値による \(d_i\) の確定
\(A_i\) の値(0, 1, 2)に応じて、隣接する関係 \(d_{i-1}\) と \(d_i\) について以下のことが言えます。
- \(A_i = 0\) の場合: \(d_{i-1} = -1\) かつ \(d_i = 1\) である必要があります。 (席 \(i\) は局所的な最小値:\(P_{i-1} > P_i < P_{i+1}\))
- \(A_i = 2\) の場合: \(d_{i-1} = 1\) かつ \(d_i = -1\) である必要があります。 (席 \(i\) は局所的な最大値:\(P_{i-1} < P_i > P_{i+1}\))
- \(A_i = 1\) の場合: \((d_{i-1}, d_i)\) は \((1, 1)\) または \((-1, -1)\) のいずれかです。 つまり、\(d_i = d_{i-1}\) でなければなりません。 (席 \(i\) は単調増加または減少の途中:\(P_{i-1} < P_i < P_{i+1}\) または \(P_{i-1} > P_i > P_{i+1}\))
3. 解の存在判定と構築
もし全ての \(A_i\) が \(1\) であれば、すべての \(d_i\) が等しくなります。これは \(P_1 < P_2 < \dots < P_N < P_1\)(またはその逆)を意味し、円環上では矛盾するため解は存在しません。
少なくとも一つの \(A_i\) が \(0\) または \(2\) であれば、そこを起点にすべての \(d_i\) を一意に決定できます。決定した \(d_i\) がすべての \(A_i\) の条件を満たしているかを確認し、矛盾がなければ、それらの大小関係を向き付きの辺としたグラフを構築します。
このグラフが閉路を持たなければ(DAGであれば)、トポロジカルソートによってスコア \(1 \dots N\) を割り振る順番を決定できます。
アルゴリズム
- 起点の探索: \(A_i \in \{0, 2\}\) となる席 \(i\) を探す。見つからなければ
-1を出力。 - 関係 \(d_i\) の決定:
- 起点となる席 \(i\) について、\(A_i\) から \(d_{i-1}\) と \(d_i\) を決める。
- そこから時計回りに、各 \(j\) について \(A_j\) を用いて \(d_j\) を順次決定していく。
- 矛盾チェック: 決定した \(d\) の集合が、すべての \(A_i\) の条件と矛盾しないか確認する。矛盾があれば
-1。 - グラフ構築とトポロジカルソート:
- \(d_i = 1\) なら 席 \(i \to\) 席 \(i+1\) の辺を張る。
- \(d_i = -1\) なら 席 \(i+1 \to\) 席 \(i\) の辺を張る。
- 入次数 \(0\) のノードから順にキューに入れる Kahn のアルゴリズムなどでトポロジカルソートを行う。
- 結果の出力: トポロジカル順序に従って \(1, 2, \dots, N\) のスコアを割り当てる。閉路が存在(ソート結果が \(N\) 要素未満)した場合は
-1。
計算量
- 時間計算量: \(O(N)\)
- \(d_i\) の決定、矛盾チェック、トポロジカルソートの各工程がすべて \(O(N)\) で行えます。
- 空間計算量: \(O(N)\)
- 隣接リストやトポロジカルソート用の配列に \(O(N)\) のメモリを使用します。
実装のポイント
円環の扱い: インデックスの計算には
(i + 1) % Nや(i - 1 + N) % Nを使用して、範囲外にならないよう注意します。トポロジカルソート: グラフの構築時に、辺の向きを「小さい方から大きい方へ」固定することで、ソート順がそのままスコアの昇順(\(1, 2, \dots, N\))に対応するようにします。
ソースコード
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
/**
* Problem Analysis:
* For each seat i, let d[i] represent the relationship between P[i] and P[i+1].
* Define d[i] = 1 if P[i] < P[i+1] and d[i] = -1 if P[i] > P[i+1].
* All indices are taken modulo N.
* The number of neighbors of seat i with score smaller than P[i], denoted as A[i], is:
* A[i] = (d[i-1] == 1 ? 1 : 0) + (d[i] == -1 ? 1 : 0).
*
* From this, we can deduce:
* - If A[i] = 0, then d[i-1] = -1 and d[i] = 1.
* - If A[i] = 2, then d[i-1] = 1 and d[i] = -1.
* - If A[i] = 1, then d[i] must be equal to d[i-1].
*
* If all A[i] = 1, no solution exists because a circular sequence cannot be strictly monotonic.
* Otherwise, we can determine all d[i] values and check for consistency.
* If consistent, the relationships P[i] < P[i+1] or P[i] > P[i+1] form a Directed Acyclic Graph (DAG).
* Any topological sort of this DAG gives an ordering for the scores P.
*/
int main() {
// Optimize I/O operations
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N;
if (!(cin >> N)) return 0;
vector<int> A(N);
int start_node = -1;
for (int i = 0; i < N; ++i) {
cin >> A[i];
// Identify a seat that must be a local minimum or maximum
if (A[i] == 0 || A[i] == 2) {
start_node = i;
}
}
// If all A[i] are 1, no solution exists
if (start_node == -1) {
cout << -1 << endl;
return 0;
}
// Determine the relative order sequence d
vector<int> d(N);
// Initialize d[start_node-1] based on A[start_node]
if (A[start_node] == 0) {
d[(start_node - 1 + N) % N] = -1;
} else {
d[(start_node - 1 + N) % N] = 1;
}
// Propagate d values around the circle
for (int k = 0; k < N; ++k) {
int j = (start_node + k) % N;
int prev = (j - 1 + N) % N;
if (A[j] == 0) {
d[j] = 1;
} else if (A[j] == 2) {
d[j] = -1;
} else {
// If A[j] == 1, d[j] must match d[prev]
d[j] = d[prev];
}
}
// Verify if the generated sequence d is consistent with all A[i]
for (int j = 0; j < N; ++j) {
int prev = (j - 1 + N) % N;
int count = (d[prev] == 1) + (d[j] == -1);
if (count != A[j]) {
cout << -1 << endl;
return 0;
}
}
// Build the Directed Acyclic Graph based on d
vector<vector<int>> adj(N);
vector<int> in_degree(N, 0);
for (int i = 0; i < N; ++i) {
int u = i;
int v = (i + 1) % N;
if (d[i] == 1) { // P[i] < P[i+1]
adj[u].push_back(v);
in_degree[v]++;
} else { // P[i] > P[i+1]
adj[v].push_back(u);
in_degree[u]++;
}
}
// Perform topological sort (Kahn's algorithm)
queue<int> q;
for (int i = 0; i < N; ++i) {
if (in_degree[i] == 0) q.push(i);
}
vector<int> topo;
while (!q.empty()) {
int u = q.front();
q.pop();
topo.push_back(u);
for (int v : adj[u]) {
if (--in_degree[v] == 0) q.push(v);
}
}
// Check if a valid topological sort exists for all nodes
if (topo.size() < (size_t)N) {
cout << -1 << endl;
} else {
// Assign scores 1 to N based on the topological order
vector<int> P(N);
for (int i = 0; i < N; ++i) {
P[topo[i]] = i + 1;
}
// Output the resulting permutation
for (int i = 0; i < N; ++i) {
cout << P[i] << (i == N - 1 ? "" : " ");
}
cout << endl;
}
return 0;
}
この解説は gemini-3-flash-thinking によって生成されました。