Official

D - Repeated Sequence Editorial by MMNMM


\(A\) の連続する部分列 \((A _ L,A _ {L+1},\dotsc,A _ R)\) の和として \(S\) が存在することと、\(\displaystyle\sum _ {i=1} ^ NA _ i=X\) について \(A\) の連続する部分列 \((A _ l,A _ {l+1},\dotsc,A _ r)\) の和として \(S\) を \(X\) で割った余り \(s\) が存在することが同値であることを示します。

証明

\([\Rightarrow]\) \(S\geq X\) ならば \(S-X\) が存在することを言えばよいです。\(S\geq X\) ならば \(R-L+1\geq N\) です(そうでなければ、\(A _ i\) が正であることに反します)。すると、\((A _ L,A _ {L+1},\dotsc,A _ {R-N})\) の総和は \(S-X\) と等しいです。\(\square\)

\([\Leftarrow]\) \((A _ l,A _ {l+1},\dotsc,A _ {r+\lfloor S/X\rfloor N})\) の総和が \(S\) と等しいです。\(\square\)

よって、\(0\leq S\lt X\) なる問題へ帰着することができました。 特に、条件を満たす連続部分列が存在すればその長さが \(N\) 未満であることがわかります。

よって、次のような問題を解けばよいです。

列 \((A _ 1,A _ 2,\dotsc,A _ {2N})\) の連続する部分列であって、その総和が \(S\) と等しいものは存在するか?

累積和 \(\displaystyle S _ i=\sum _ {1\leq j\leq i}A _ j\quad(0\leq i\leq 2N)\) を考えることで、さらに次のような問題に帰着されます。

列 \((S _ 0,S _ 1,\dotsc,S _ {2N})\) の元 \(S _ i,S _ j\) であって、\(S _ j-S _ i=S\) を満たすものは存在するか?

これは、列 \((S _ 0,S _ 1,\dotsc,S _ {2N})\) に出現するすべての整数を平衡二分探索木やハッシュテーブルで管理したり、尺取り法を用いたりすることで \(O(N\log N)\) や \(O(N)\) 時間で解くことができます。

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

この実装例では std::set を用いて問題を解いているため、時間計算量は \(O(N\log N)\) となっています。

#include <iostream>
#include <numeric>
#include <set>
#include <vector>

int main() {
    using namespace std;
    unsigned N;
    unsigned long S;
    cin >> N >> S;

    vector<unsigned long> A(N);
    for (auto& a : A)
        cin >> a;

    // 0 <= S < X に帰着
    const auto sum{reduce(begin(A), end(A))};
    S %= sum;

    // A の長さを倍にする
    A.reserve(2 * N);
    for (unsigned i{}; i < N; ++i)
        A.emplace_back(A[i]);

    // 累積和を計算
    set<unsigned long> prefix_sum{};
    prefix_sum.emplace();
    for (unsigned long p{}; const auto a : A) {
        p += a;
        prefix_sum.insert(p);
    }

    // 累積和の要素 p について p + S が含まれているか判定
    for (const auto p : prefix_sum)
        if (prefix_sum.contains(p + S)) {
            cout << "Yes" << endl;
            return 0;
        }

    cout << "No" << endl;
    return 0;
}

posted:
last update: