はまやんはまやんはまやん

hamayanhamayan's blog

直列大学 [yukicoder 1043]

https://yukicoder.me/problems/no/1043

前提知識

解説

https://yukicoder.me/submissions/475040

初手がなかなか難しいし、色々なテクを必要とするので、少し難しい問題。
逆に言うと理解すれば色々な技を得られるだろう。
制約を見るとそんなに大きくないし、109+7数え上げなんで、まずはDPかなぁ…で考える。
今回は当てが当たったというか、ぶっ飛んで考察進んだので、行間があんまない。

DPフェーズ

DPをする。
dp[i][tot] := i番目までの乾電池を使って合計電圧をtotにする選び方の組合せ
合計電圧の最大は105なので、状態数が102×105=107となる。
これは全部メモリを用意するには少し心配。
更新式はdp[i+1][tot] = dp[i][tot] + dp[i][tot - V[i]]であるが…

と、ここで1つ典型がある。
DPの空間削減テクというのがあって、今回のようにdp[i+1]がdp[i]のみから更新できるときに、
dp[i][tot]と定義しなくてもdp[tot]だけで済むという空間削減テクがある。
(このテクを知らない場合は、まずdp[i][tot]で実装するのがオススメ。それの変形で書ける)
動的計画法における空間計算量削減テク - hogecoder
ここを見て学習しよう。一応以下にも簡単に概要は書いておく。

一般に空間が削減されると、処理が高速になるので、やれるときはやるといい。
さて、どうやってやるかというと、DPは以下のように定義する。
dp[tot] := 合計電圧をtotにする選び方の組合せ
i番目を順番に評価していくのは同じなのだが、totを降順に更新していく。
すると、dp[tot] += dp[tot - V[i]]と更新式を改めることができる。
これはdp[tot]を更新しようとしている場面では降順にtotを見ているので、dp[tot - V[i]]は実質dp[i][tot - V[i]]ということになる。
それをdp[i][tot]に加算しているので、元の更新式と一致するという寸法である。

このDPを乾電池でも豆電球でもしておく。
dp1[tot] := 合計電流がtotである乾電池の組合せ
dp2[tot] := 合計抵抗がtotである豆電球の組合せ

累積和フェーズ

なんで累積和が出てくるんだという感じだと思う。
条件としてA≦電流/抵抗≦Bを満たす必要があるが、変数が2つもあるとやりにくい。
抵抗を全探索することで解決しよう。
抵抗をrとして固定すると、条件はAr≦電流≦Brを満たす必要がある。
L=Ar, R=Brとすると、
抵抗がrのときに条件を満たす組み合わせはdp2[r] * (dp1[L] + dp1[L+1] +... + dp1[R-1] + dp1[R])となる。
このdp1区間和は累積和で求めることができる。

dp1を累積和に変換しておこう。
dp1[tot] := 合計電流がtot以下である乾電池の組合せ
すると、条件を満たす組み合わせはdp2[r] * (dp1[R] - dp1[L - 1])となる。
あとは、[1,105]の範囲でrを動かして組合せを足し合わせると答えになる。

#define DPMAX 101010
int N, M, V[101], R[101], A, B;
mint dp1[DPMAX], dp2[DPMAX];
//---------------------------------------------------------------------------------------------------
void dodp(int n, int* v, mint* dp) {
    dp[0] = 1;
    rep(i, 0, n) rrep(tot, DPMAX - 1, 0) if (0 <= tot - v[i]) dp[tot] += dp[tot - v[i]];
}
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> M;
    rep(i, 0, N) cin >> V[i];
    rep(i, 0, M) cin >> R[i];
    cin >> A >> B;

    dodp(N, V, dp1);
    rep(v, 1, DPMAX) dp1[v] += dp1[v - 1];

    dodp(M, R, dp2);

    mint ans = 0;
    rep(r, 1, DPMAX) {
        ll L = 1LL * A * r;
        ll R = 1LL * B * r;

        // [L, R]
        if (DPMAX <= L) continue;
        R = min(R, 1LL * DPMAX - 1);

        ans += dp1[R] * dp2[r];
        if (0 < L) ans -= dp1[L - 1] * dp2[r];
    }
    cout << ans << endl;
}