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: