题目链接:SPOJ 16607

John 有 $n$ 个水果罐子,每个罐子都装有不同种类的糖果,第 $i$ 个罐子里有 $m_i$ 个糖果。John 决定吃一些糖果,并且打算至少吃 $a$ 个,至多吃 $b$ 个,求一共有多少种吃法。答案对 $2004$ 取模。

数据范围:$1 \le n \le 10$,$0 \le a \le b \le 10 ^ 7$,$0 \le m_i \le 10 ^ 7$。


Solution

首先把问题转化为 $0 \sim b$ 的答案减去 $0 \sim a - 1$ 的答案。

发现 $n$ 非常小,我们可以考虑容斥,每次强制一个集合内的罐子取超出数量的糖果,其他罐子里随便取。

对于子问题:至多取 $m$ 颗糖果,一共有 $n$ 种糖果。我们考虑把 $m$ 分为 $n + 1$ 个部分,其中最后一个部分表示不选的糖果,显然有 $a_1 + a_2 + \dots + a_{n + 1} = m$ 且 $a_i \ge 0$,通过隔板法可以得到答案:

$$ \binom{(m + n + 1) - 1}{(n + 1) - 1} = \binom{m + n}{n} $$

最后还有一个问题:模数不为质数!

有一个结论:在 $b \mid a$ 的情况下,有 $\frac{a}{b} \equiv \frac{a \bmod (bp)}{b} \pmod p$,证明非常显然因此不再赘述。

时间复杂度:$\mathcal O(2 ^ n \cdot n)$。


Code

#include <cstdio>

const int N = 15;
const int MOD = 2004;

int n, l, r, fac = 1, a[N];

void add(int &x, int y) {
    (x += y) >= MOD && (x -= MOD);
}
int pow(int x, int k) {
    int ans = 1;
    for (; k; k >>= 1, x = 1LL * x * x % MOD) {
        if (k & 1) ans = 1LL * ans * x % MOD;
    }
    return ans;
}
int C(int n, int m) {
    long long mod = 1LL * MOD * fac, ans = 1;
    for (int i = n - m + 1; i <= n; i++) ans = ans * i % mod;
    return ans / fac % MOD;
}
int solve(int lim) {
    int ans = 0;
    for (int s = 0; s < (1 << n); s++) {
        int cnt = 0;
        for (int i = 1; i <= n; i++) {
            if (s & (1 << (i - 1))) cnt += a[i] + 1;
        }
        if (cnt > lim) continue;
        int now = C(lim - cnt + n, n);
        add(ans, __builtin_popcount(s) & 1 ? (MOD - now) : now);
    }
    return ans;
}
int main() {
    scanf("%d%d%d", &n, &l, &r);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]), fac *= i;
    printf("%d\n", (solve(r) - solve(l - 1) + MOD) % MOD);
    return 0;
}
最后修改:2019 年 07 月 06 日
如果觉得我的文章对你有用,请随意赞赏