Official

E - K-Divisible Subarrays Editorial by MMNMM Editorial for printing


\(A\) の\({}\bmod K\) における累積和 \(\displaystyle S\ (S _ i=\sum _ {j=1} ^ iA _ i\pmod K)\) によって条件を言い換えると、次のような問題になります。

整数列のスコアを、隣接する等しい値の個数として定める。長さ \(N+1\) の列 \(S=(S _ 0,S _ 1,\ldots,S _ N)\) の部分列のうち \(S _ 0,S _ N\) を含むものにおけるスコアの最大値を求めよ。

\(S _ 0,S _ N\) を取り除いてスコアが大きくなることはないので、すべての部分列をわたる最大値としても答えは変わりません。

スコアが \(k\) である \(S\) の部分列について、隣接する等しい値の組を \((S _ {l _ i},S _ {r _ i}) _ {i=1,2,\ldots,k}\) とすると、\(k\) 個の右半開区間 \(\lbrack l _ i,r _ i\rparen\) は互いに共通部分を持ちません。 逆に、共通部分を持たないように \(S _ l=S _ r\) なる右半開区間 \(\lbrack l,r\rparen\) を \(k\) 個選べるなら、それらの端点からなる部分列を取ることでスコアを \(k\) にすることができます。

よって、さらに次のような問題に言い換えることができます。

整数列 \(S\) に対して、区間の集合 \(I\coloneqq\lbrace\lbrack l,r\rparen\mid S _ l=S _ r\rbrace\) を定める。 \(I\) の部分集合であって、どの \(2\) つの区間も共通部分をもたないものの大きさの最大値を求めよ。

これは区間スケジューリング問題と呼ばれる問題で、貪欲法によって解くことができます。 このままでは \(I\) の大きさが \(\Theta(N ^ 2)\) になってしまう場合がありますが、\(S _ l\) ごとに一番左側の \(S _ r\) をとることで答えを変えないまま \(O(N)\) 個の区間に削減することができます(\(S _ l=S _ m=S _ r\ (l\lt m\lt r)\) であるような \(l,m,r\) について、\(\lbrack l,r\rparen\) を選ぶことができるなら \(\lbrack l,m\rparen\) と \(\lbrack m,r\rparen\) の \(2\) つを選んだほうがよいため)。

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

#include <iostream>
#include <map>
#include <ranges>

int main() {
    using namespace std;
    unsigned N, K;
    cin >> N >> K;
    
    unsigned ans{};

    // S の値からいつ出現したかを求める連想配列
    map<unsigned, unsigned> prev;
    prev[0] = 0;
    for (unsigned S{}, last{}; const auto &[i, A] : views::istream<unsigned>(cin) | views::enumerate) {
        (S += A) %= K; // 累積和を求めて
        // 累積和の値がすでに出現していて、かつ選んだ区間と重なっていなければ
        if (prev.contains(S) && last <= prev[S]) {
            ++ans;
            last = i + 1; // 区間を選ぶ
        }
        prev[S] = i + 1; // 番号を更新
    }
    
    cout << ans << endl;
    return 0;
}

posted:
last update: