[JSOI2011]分特产

可以发现这样一件事情,不同种类的特产之间对方案是不会有影响的,因此我们可以每次考虑将一种特产分配给所有人。于是就有了一个 \(dp\),令 \(dp_{i, j}\) 表示考虑完前 \(i\) 种特产,已经有 \(j\) 个人分配到了特产的方案。转移时考虑当前新增几个人获得特产,先将当前特产给这些人每个分配 \(1\) 再考虑将这些特产分配给当前 \(j\) 个人,因为同种特产都是一样的,两个方案不同当前仅当存在某个人分配的数量不同,这相当于问方程 \(x_1 + x_2 + \cdots x_j = k\) 的解非负整数解数量,直接使用插板法解决,形式化地就是(令第 \(i\) 种特产有 \(a_i\) 个):

\[dp_{i, j} = \sum\limits_{k = 0} ^ {\min(a_i, j)} \dbinom{m - j + k}{k} \times \dbinom{a_i - k + j - 1}{j - 1} dp_{i - 1, j - k} \]

\(dp_{m, n}\) 即为答案。

#include<bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for(int i = l; i <= r; ++i)
const int N = 1000 + 5;
const int Mod = 1000000000 + 7;
int n, m, a[N], dp[N][N], C[N * 2][N * 2];
int read(){
    char c; int x = 0, f = 1;
    c = getchar();
    while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int Inc(int a, int b){
    return (a += b) >= Mod ? a - Mod : a;
}
int Mul(int a, int b){
    return 1ll * a * b % Mod;
}
int main(){
    n = read(), m = read();
    rep(i, 1, m) a[i] = read();
    rep(i, 0, (N - 5) * 2) C[i][0] = 1;
    rep(i, 1, (N - 5) * 2) rep(j, 1, i) C[i][j] = Inc(C[i - 1][j - 1], C[i - 1][j]);
    dp[0][0] = 1, random_shuffle(a + 1, a + m + 1);
    rep(i, 1, m) rep(j, 1, n) rep(k, 0, min(j, a[i])){
        dp[i][j] = Inc(dp[i][j], Mul(Mul(C[n - j + k][k], C[a[i] - k + j - 1][j - 1]), dp[i - 1][j - k]));
    }
    printf("%d", dp[m][n]);
    return 0;
}

但是上面这个 \(dp\) 还是 \(O(n ^ 3)\) 的,还不能通过本题。可以发现上面这个 \(dp\) 实际上是逐步确保每个人都有特产可选的过程,本质上就是恰好有 \(n\) 个人都有特产的方案。对于这种恰好,我们可以考虑使用二项式反演(也就是容斥)。但是会发现这样一个问题,钦定有 \(i\) 个位置一定有特产的方案实际上又是原来的问题,这时我们的一般思路就是反过来考虑求恰好有 \(0\) 个位置满足没有特产,因为没有特产是很好保证的。于是我们可以令 \(f_i\) 表示钦定有 \(i\) 个位置没有特产的方案,则:

\[f_i = \dbinom{n}{i} \prod\limits_{j = 1} ^ m \dbinom{a_i + n - i - 1}{n - i - 1} \]

\(g_i\) 表示恰好有 \(i\) 个位置没有特产的方案,那么可以推导出 \(f, g\) 的关系:

\[f_i = \sum\limits_{j = i} ^ n \dbinom{j}{i} g_j \]

则:

\[g_0 = \sum\limits_{i = 0} ^ n (-1) ^ i \dbinom{n}{i} \prod\limits_{j = 1} ^ m \dbinom{a_i + n - i - 1}{n - i - 1} \]

直接计算即可。

#include<bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for(int i = l; i <= r; ++i)
const int N = 2000 + 5;
const int Mod = 1000000000 + 7;
int n, m, ans, tmp, a[N], C[N][N];
int read(){
    char c; int x = 0, f = 1;
    while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int Inc(int a, int b){
    return (a += b) >= Mod ? a - Mod : a;
}
int Dec(int a, int b){
    return (a -= b) < 0 ? a + Mod : a;
}
int Mul(int a, int b){
    return 1ll * a * b % Mod;
}
int main(){
    n = read(), m = read();
    rep(i, 1, m) a[i] = read();
    rep(i, 0, N - 5) C[i][0] = 1;
    rep(i, 1, N - 5) rep(j, 1, i) C[i][j] = Inc(C[i - 1][j - 1], C[i - 1][j]);
    rep(i, 0, n){
        tmp = 1;
        rep(j, 1, m) tmp = Mul(tmp, C[a[j] + n - i - 1][n - i - 1]);
        if(i & 1) ans = Dec(ans, Mul(tmp, C[n][i]));
        else ans = Inc(ans, Mul(tmp, C[n][i]));
    }
    printf("%d", ans);
    return 0;
}
posted @ 2020-09-02 21:23  Achtoria  阅读(177)  评论(0编辑  收藏  举报