AOJ 2333 : 僕の友達は小さい My friends are small (JAG Summer 2010)

まずは簡単な例外的な組合せから数えてみる。以下、解をanswerと表記する。どの友達もリュックに入れることができないならばanswer+=1。また、すべての友達をリュックに入れることができるならばanswer+=1。

それ以外の場合を数える。とりあえずリュックに入れない友達の最小の重さmを総当たりにより固定する。これはたかだかN通り。このとき重さm未満の友達はすべてリュックに入るはずだから、少なくとも \sum_{w\lt m}wの重さの友達をリュックに入れる。さて、注意しなければいけないのは、重さmの友達が1人とは限らないことである。重さmの友達の人数を g(m)と表記することにする。重さmの友達を x\lt g(m)人選ぶ方法は \binom{g(m)}{x}通りである。このとき重さの和は、重さm未満の友達も考慮に入れると \sum_{w\lt m}w + xmである。

mごとにDPを解く。

dp(i, j) := 「重さi-1の友達の状態まで確定した時、リュックに入った友達の重さの和がjである。そのような場合の数」

とする。 これを先の \sum_{w\lt m}w + xmによってDPを初期化してから計算していく。重さmの友達が入れられない状態のみを計算するので  answer += \sum_{m>W-j}dp(最後の友達まで確定,  j)とする。

計算量は全体でO(WN2)

vector<vector<int>> combinations(int n, int mod) {
    auto res = vector<vector<int>>(n + 1, vector<int>(n + 1));
    rep(i, n + 1) res[i][0] = 1;
    for (int i = 1; i <= n; ++i) for (int j = 1; j <= i; ++j)
        res[i][j] = (res[i - 1][j - 1] + res[i - 1][j]) % mod;
    return res;
}

int N, W, wt[201], M;
vector<vi> C;
vector<pii> we;
mint dp[201][10001];

mint f(int s) {
    int mi = we[s].first;
    FOR(i, s + 1, M) {
        int w, num;
        tie(w, num) = we[i];
        rep(j, num + 1) {
            rep(k, W + 1) {
                if (j*w + k <= W) {
                    dp[i + 1][j*w + k] += dp[i][k] * C[num][j];
                }
            }
        }
    }
    mint re;
    FOR(i, 1, W + 1) {
        if (mi > W - i)re += dp[M][i];
    }
    return re;
}

int main() {
    C = combinations(200, 1000000007);

    cin >> N >> W;
    rep(i, N) {
        cin >> wt[i];
    }
    sort(wt, wt + N);

    int pre = -1;
    rep(i, N) {
        if (wt[i] != pre) {
            we.push_back(mp(wt[i], 0));
        }
        we.back().second++;
        pre = wt[i];
    }

    mint re;
    M = sz(we);
    int sm = 0;
    rep(i, M) {
        MEM(dp, 0);
        int num = we[i].second;
        rep(j, num) {
            if (sm <= W) {
                dp[i + 1][sm] += C[num][j];
            }
            sm += we[i].first;
        }
        re += f(i);
    }
    if (sm <= W)re += 1;
    if (wt[0] > W)re += 1;
    cout << re << endl;
}