公式

C - 部活動の選択 / Choosing a Club Activity 解説 by admin

DeepSeek V3

概要

複数の部活動から1つ以上を選び、活動ポイントの合計がKの倍数となる組み合わせの数を求める問題です。

考察

この問題は部分和問題の変種で、合計値がKの倍数となる組み合わせを数えるものです。素朴なアプローチとしては、全ての部分集合(\(2^N\)通り)を列挙して合計値がKの倍数かどうかをチェックする方法が考えられますが、Nが最大\(10^5\)となるため、\(2^{100000}\)通りは現実的な時間では計算できません。

重要な観察として、Kの値が最大100と比較的小さいことに注目します。合計値そのものではなく、合計値をKで割った余りだけを管理することで、状態数を大幅に削減できます。

アルゴリズム

動的計画法(DP)を用います。DP配列をサイズKで定義し、dp[j]はこれまでに選んだ部活動の合計ポイントをKで割った余りがjとなる組み合わせの数を表します。

各部活動について、その活動ポイントをKで割った余りrを計算し、現在のDP状態から新しい状態を更新します。具体的には、各余りjに対して、新しい余り (j + r) % K の位置に現在の組み合わせ数を加算します。

初期状態として、何も選んでいない状態(余り0)を1通りとし、最後に空集合(何も選ばない場合)を除外するためにdp[0]から1を引きます。

計算量

  • 時間計算量: \(O(N \times K)\)
  • 空間計算量: \(O(K)\)

Nが\(10^5\)、Kが100なので、\(10^5 \times 100 = 10^7\)回の操作で済み、制限時間内に実行可能です。

実装のポイント

  • DP配列のサイズはKで十分

  • 各ステップで現在のDP配列をコピーしてから更新を行う(インプレース更新による状態の上書きを防ぐ)

  • モジュロ演算\(10^9 + 7\)を適切に行う

  • 最後に空集合分を引くことを忘れない

    ソースコード

MOD = 10**9 + 7

def main():
    import sys
    data = sys.stdin.read().split()
    n = int(data[0])
    K = int(data[1])
    A = list(map(int, data[2:2+n]))
    
    dp = [0] * K
    dp[0] = 1
    
    for a in A:
        r = a % K
        new_dp = dp[:]
        for j in range(K):
            if dp[j] > 0:
                new_r = (j + r) % K
                new_dp[new_r] = (new_dp[new_r] + dp[j]) % MOD
        dp = new_dp
    
    print((dp[0] - 1) % MOD)

if __name__ == '__main__':
    main()

この解説は deepseekv3 によって生成されました。

投稿日時:
最終更新: