B - 整列の部分修正 / Partial Correction of a Sorted Sequence Editorial by admin
gemini-3-flash-thinking概要
現在の生徒の並び順 \(p\) を、連続する区間の反転操作をちょうど1回行うことで、目標の並び順 \(q\) に一致させられるかを判定する問題です。
考察
この問題を解くための重要なポイントは、反転すべき区間の候補を絞り込むことです。
1. すでに一致している場合
もし最初から \(p\) と \(q\) が完全に一致している場合、問題文の「\(L=R\) の場合は並びは変化しませんが、これも 1 回の操作を行ったものとみなします」というルールにより、どの位置でも良いので \(L=R\) として操作を行えば目標を達成できます。したがって、この場合は常に Yes です。
2. 一致していない場合
\(p\) と \(q\) が異なる場合、少なくとも 1 箇所以上の要素が異なっています。 反転操作によって \(p\) を \(q\) に一致させるためには、「\(p\) と \(q\) が異なっている全ての範囲」を反転させる必要があります。
具体的には、以下の手順で反転候補となる区間 \([L, R]\) を特定できます。 - 左端から見ていき、最初に \(p_i \neq q_i\) となるインデックスを \(L\) とする。 - 右端から見ていき、最初に \(p_i \neq q_i\) となるインデックスを \(R\) とする。
もし 1 回の反転で一致させることが可能なら、反転すべき区間はこの \([L, R]\) 以外にありえません。なぜなら: - \(L\) より左や \(R\) より右の要素を反転に含めてしまうと、元々一致していた要素が別の場所に移動してしまいます。 - \(p\) と \(q\) は順列(1から \(N\) が1回ずつ現れる)であるため、一度一致している場所を動かして別の要素を持ってくると、そこは必ず不一致になってしまいます。
したがって、「特定した区間 \([L, R]\) を反転させた後、配列全体が \(q\) と一致するか」を確認するだけで判定が可能です。
アルゴリズム
- 配列 \(p\) と \(q\) を読み込む。
- 左から順に比較し、最初に \(p[i] \neq q[i]\) となるインデックス \(L\) を探す。
- 全て一致していれば、
Yesを出力して終了する。 - 右から順に比較し、最初に \(p[i] \neq q[i]\) となるインデックス \(R\) を探す。
- \(p\) の \(L\) 番目から \(R\) 番目までの要素を反転させる。
- 反転後の \(p\) が \(q\) と完全に一致するかを確認する。一致すれば
Yes、そうでなければNoを出力する。
計算量
\(N\) が最大 \(10^6\) と大きいため、効率的な処理が必要です。 - 時間計算量: \(O(N)\) - 配列の走査(\(L, R\) の特定)、反転、および最後の比較はすべて \(O(N)\) で行えます。 - 空間計算量: \(O(N)\) - 長さ \(N\) の配列を 2 つ保持するために必要です。
実装のポイント
\(N=10^6\) のため、入出力がボトルネックにならないよう C++ では
ios_base::sync_with_stdio(false); cin.tie(NULL);を使用して高速化を行います。std::reverse(p.begin() + L, p.begin() + R + 1);を使うことで、指定した範囲を効率よく反転できます。vector同士の比較p == qは \(O(N)\) で行えるため、反転後のチェックに便利です。ソースコード
#include <iostream>
#include <vector>
#include <algorithm>
/**
* Problem: Determine if a permutation p can be transformed into permutation q
* by reversing exactly one contiguous subarray [L, R].
*
* Approach:
* 1. If p and q are already identical, we can perform an operation where L = R.
* This satisfies the "exactly 1 operation" condition and leaves the array unchanged.
* Therefore, if p == q, the answer is "Yes".
* 2. If p and q are different, let L_min be the first index where p[i] != q[i]
* and R_max be the last index where p[i] != q[i].
* 3. Any single reversal that transforms p into q must cover at least the range [L_min, R_max].
* 4. Since p and q are permutations (all elements are distinct), any reversal that
* includes elements outside [L_min, R_max] would swap elements that already match,
* making them no longer match (unless the segment is trivial, which brings us back to [L_min, R_max]).
* 5. Thus, we only need to check if reversing the specific segment [L_min, R_max] in p results in q.
*
* Complexity:
* - Time: O(N), where N is the number of students (up to 10^6).
* - Space: O(N) to store the permutations.
*/
using namespace std;
int main() {
// Fast I/O is necessary for N = 10^6
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int N;
if (!(cin >> N)) return 0;
// Read the current order p
vector<int> p(N);
for (int i = 0; i < N; ++i) {
cin >> p[i];
}
// Read the target order q
vector<int> q(N);
for (int i = 0; i < N; ++i) {
cin >> q[i];
}
// Find the first index L where p and q differ
int L = -1;
for (int i = 0; i < N; ++i) {
if (p[i] != q[i]) {
L = i;
break;
}
}
// If there are no differences, p == q.
// An operation with L = R (e.g., L=1, R=1) is allowed and results in p == q.
if (L == -1) {
cout << "Yes" << endl;
return 0;
}
// Find the last index R where p and q differ
int R = -1;
for (int i = N - 1; i >= 0; --i) {
if (p[i] != q[i]) {
R = i;
break;
}
}
// The only candidate operation that could potentially transform p into q
// is reversing the segment from index L to R.
reverse(p.begin() + L, p.begin() + R + 1);
// After performing the reversal, check if the entire array p matches q.
// In C++, comparing two vectors takes O(N) time.
if (p == q) {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
return 0;
}
この解説は gemini-3-flash-thinking によって生成されました。
posted:
last update: