No.269 見栄っ張りの募金活動 | yukicoder

愚直なDPを考えてみる。

f(i, j, k) = 「i人目まで確定して、前の人がj円払った時、合計金額がk円である場合の数」

これでは間に合わない。

とりあえず、最初の人の募金額を0~Sの範囲で総当たりして固定し見てよう。先の人も含めて、少なくともその額を支払う必要がある。つまり、下図の斜線部は必ず支払われる。

f:id:parukii:20180704140039j:plain

後の人々についても、その時点での支払確定の合計金額について注目することにする。先のDPの合計金額を、先の人も含む斜線部の支払い確定の金額に置き換えれば、前の人の支払った金額j円は情報として不要になる。というのは、この置き換えによって、(前の人の支払った金額+K)円以上ではなく、K円以上の範囲で支払い額を決めればよくなったため。

よって以下のようなDPを得る。

g(i, j) = 「i人目まで確定して、支払い確定の合計金額がj円である場合の数」

 g(i, j) = \sum_{k*(N-i) \leq j} g(i-1, j-k(N-i)) で求まる。一見したところ全体でO(NS2)ほどの計算量がありそうだが、実際には最悪ケースで109程度の計算量で済み、時間制限が5秒もあるので間に合う。

int N, S, K, M = (int)1e9+7;
cin >> N >> S >> K;
vi cu(S + 1), ne(S + 1);
int p = 0;
while (p*N <= S)cu[p++*N] = 1;
FOR(i, 1, N) {
    fill(all(ne), 0);
    rep(j, S + 1) {
        FOR(k, K, S + 1) {
            int l = j + k * (N - i);
            if (l > S)break;
            ne[l] += cu[j];
            if (ne[l] >= M)ne[l] -= M;
        }
    }
    swap(cu, ne);
}
cout << cu[S] << endl;