Official

C - Move One Card Editorial by MMNMM Editorial for printing


問題文中の操作は次のように言い換えることができます。

  • \(A\) のある区間 \([l,r]\) をとり、\(A _ l,A _ {l+1},\ldots,A _ r\) をひとつ右に巡回シフトするか、ひとつ左に巡回シフトする。

以下では、操作を区間 \([l,r]\) と \(\lbrace\)左\(,\) 右\(\rbrace\) の組として表すこととします。

答えが Yes のとき、\(A\) を \(B\) に一致させる操作の \([l,r]\) がどのような条件を満たすか考えます。

はじめから \(A=B\) のとき、\(l=r\) とすればよいです。 以下でははじめ \(A\ne B\) であるときについて考えます。

\(A _ i\ne B _ i\) となる最小の \(i\) を \(m\) 、最大の \(i\) を \(M\) とします。 \(m\in[l,r]\) でなければ \(A _ m\) の値は変化しないため、\(m\in[l,r]\) です。 同様に \(M\in[l,r]\) なので、\([m,M]\subseteq[l,r]\) となります。

\(l\lt m\) のときについて、右に巡回シフトを行うか左に巡回シフトを行うかによって場合分けを行います。

  • 右に巡回シフトを行って \(A\) が \(B\) と一致するとき、\(A _ r=B _ l\) かつ \(A _ i=B _ {i+1}\ (l\le i\lt r)\) です。 \(m\) の定義から、\(A _ i=B _ i\ (l\le i\lt m)\) が成り立つので、\(A _ r=B _ l=A _ l=B _ {l+1}=A _ {l+1}=\cdots=A _ {m-2}=B _ {m-1}=A _ {m-1}=B _ m\) となります。 このことから、\([m,r]\) に対して右に巡回シフトを行っても得られる列が変わらないことがわかります。
  • 左に巡回シフトを行って \(A\) が \(B\) と一致するとき、\(A _ l=B _ r\) かつ \(A _ {i+1}=B _ i\ (l\le i\lt r)\) です。 \(m\) の定義から、\(A _ i=B _ i\ (l\le i\lt m)\) が成り立つので、\(B _ r=A _ l=B _ l=A _ {l+1}=B _ {l+1}=\cdots=B _ {m-2}=A _ {m-1}=B _ {m-1}=A _ m\) となります。 このことから、\([m,r]\) に対して左に巡回シフトを行っても得られる列が変わらないことがわかります。

同様に、\(M\lt r\) のときに \([l,r]\) に対して操作を行って \(A\) と \(B\) を一致させられるなら、\([l,M]\) に対して操作を行って \(A\) と \(B\) を一致させられることがわかります。

よって、答えが Yes なら \([m,M]\) に対して適切な操作を行うことで \(A\) と \(B\) を一致させられることがわかります。

以上の議論から、次のようなアルゴリズムで答えを求めることができます。

  1. \(A\) と \(B\) が空でなく、かつ \(A\) と \(B\) の末尾の要素が一致している限り、\(A\) と \(B\) の末尾から要素を取り除く。
  2. \(A\) と \(B\) が空でなく、かつ \(A\) と \(B\) の先頭の要素が一致している限り、\(A\) と \(B\) の先頭から要素を取り除く。
  3. \(A\) と \(B\) が空か、\(A\) をひとつ右に巡回シフトした列が \(B\) と等しいか、\(A\) をひとつ左に巡回シフトした列が \(B\) と等しいとき、答えは Yes 。そうでないとき、答えは No 。

これは適切に実装することで \(O(N)\) 時間で動作させることができます。

実装例は以下のようになります。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    int N;
    cin >> N;
    vector<int> A(N), B(N);
    for (auto&& a : A) cin >> a;
    for (auto&& b : B) cin >> b;
    
    // 後ろから一致している部分を削除
    while (!A.empty() && A.back() == B.back()) {
        A.pop_back();
        B.pop_back();
    }
    
    // 反転して
    ranges::reverse(A);
    ranges::reverse(B);
    // 一致している部分を削除
    while (!A.empty() && A.back() == B.back()) {
        A.pop_back();
        B.pop_back();
    }
    
    // A == B なら
    if (A.empty()) { // Yes
        cout << "Yes" << endl;
        return 0;
    }
    
    // 左にシフトした場合と
    auto A_lshift = A;
    ranges::rotate(A_lshift, begin(A_lshift) + 1);
    if (A_lshift == B) {
        cout << "Yes" << endl;
        return 0;
    }
    
    // 右にシフトした場合を試す
    auto A_rshift = A;
    ranges::rotate(A_rshift, end(A_rshift) - 1);
    if (A_rshift == B) {
        cout << "Yes" << endl;
        return 0;
    }
    
    cout << "No" << endl;
    return 0;
}

posted:
last update: