[luogu p4640] [BJWC2008]王之财宝

Link\mathtt{Link}

P4640 [BJWC2008]王之财宝 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

Description\mathtt{Description}

mm 个无区别球放进 nn 个有区别盒子,可以有不放的球,可以有空盒,其中编号 1t1 \sim t 的盒子有容量限制 bib_i,问放置方案数。对给定质数 pp 取模。

Data Range & Restrictions\mathtt{Data} \text{ } \mathtt{Range} \text{ } \mathtt{\&} \text{ } \mathtt{Restrictions}

  • 1m1091 \le m \le 10^9
  • 0tn1090 \le t \le n \le 10^9
  • t15t \le 15
  • 1<p1051 < p \le 10^5
  • 0<bi1090 <b_i \le 10^9

Solution\mathtt{Solution}

球盒问题,轻车熟路。

首先考虑简化问题到 mm 个球都放进盒子中(不能不放),可以有空盒,没有容量限制。那么方案数是 (n+m1m)\dbinom{n +m - 1}{m}.(证明参见我写过的放球问题 学习笔记 - ACodinghusky - 博客园 (cnblogs.com)

此时再考虑逐渐放宽问题一直到题目的样子,首先先将不能不放球的限制砍掉。

其实这个时候也就相当于没放的球自己独立进一个盒子,也就相当于盒子多了一个,mm+1m \leftarrow m + 1。结果为 (n+mm)=(n+mn)\dbinom{n + m}{m} = \dbinom{n + m}{n}.

现在再加入盒子容量的限制。观察到 t15t \le 15,考虑 2t2^t 状态压缩暴力枚举。

再考虑容斥原理,答案等于至少0个盒子超载方案数(上面刚刚求的)-至少任意1个盒子超载总方案数+至少任意2个盒子超载总方案数-至少任意3个盒子超载总方案数……

接下来举例说明:

总共有三个空间有限的盒子,至少 00 个盒子超载方案数:(3+m3)\dbinom{3 + m}{3}

考虑至少让 11 个盒子超载:

  • 将盒子 11 塞超,也就是塞满+1(容量+1),剩下的球在 33 个盒子里随便选着放,方案数:(3+mb113)\dbinom{3+m-b_1-1}{3}

  • 将盒子 22 塞超,剩下的球在 33 个盒子里随便选着放,方案数:(3+mb213)\dbinom{3+m-b_2-1}{3}

  • 33 塞超,…… (3+mb313)\dbinom{3 + m - b_3 - 1}{3}

第一个盒子的所有超载情况方案数就是上面三个结果的和,根据容斥的式子,总共需要减去这个和。

接下来,要至少让 22 个盒子超载:

首先把 1,31, 3 都塞超(每个盒子塞到它的容量+1),然后剩下的球在 33 个盒子里随便选着放的方案数:(3+mb11b313)\dbinom{3+m-b_1-1-b_3-1}{3}

然后把 2,32, 3 都塞超,剩下的球在 33 个盒子里随便选着放方案数:(3+mb21b313)\dbinom{3+m-b_2-1-b_3-1}{3}

最后把 1,21, 2 都塞超,方案数:(3+mb11b213)\dbinom{3+m-b_1-1-b_2-1}{3}

那么至少让 22 个盒子超载的方案数就是上面三个方案数的总和。根据容斥式子,总共需要加上这个和。

然后再让至少(总共就3个其实已经不是至少了) 33 个盒子超载……:

就是所有都塞超:(3+mb11b21b313)\dbinom{3 + m -b_1 - 1 - b_2 - 1 - b_3 - 1}{3}

根据容斥的式子总共需要减去这个东西。

最后容斥统计,可以得到结果是这样一个东西:

(3+m3)(3+mb113)(3+mb213)(3+mb313)+(3+mb11b313)+(3+mb21b313)+(3+mb11b213)(3+mb11b21b313)\dbinom{3 + m}{3} - \dbinom{3+m-b_1-1}{3} - \dbinom{3+m-b_2-1}{3} -\dbinom{3 + m - b_3 - 1}{3} + \dbinom{3+m-b_1-1-b_3-1}{3} + \\ \quad\\\dbinom{3+m-b_2-1-b_3-1}{3} + \dbinom{3+m-b_1-1-b_2-1}{3} - \dbinom{3 + m -b_1 - 1 - b_2 - 1 - b_3 - 1}{3}

到这里思路也就讲完了,但是还有一个东西:

考虑到上述这个式子就是很多的组合数相加相减,同时观察到这些组合数一一对应了所有 tt 个盒子能构成的 2t2 ^ t 种选法。

实际上我们只需要暴力枚举 2t2^t,表示一种选法状态,计算出对应的组合数,然后根据选的数量的奇偶性决定它在总式子里做加数还是减数

这部分的代码:

    for (int i = 0; i < (1 << t); ++i) {
        int cnt = 0, k = m;
        for (int j = 1; j <= t; ++j)
            if (i & (1 << j - 1)) {
                ++cnt;
                k -= b[j] + 1;
            }
        int g = lucas(n + k, n);
        (ans += (cnt & 1) ? p - g : g) %= p;
    }

对于组合数,考虑到 pp 最大到 10510^5,使用Lucas定理求解。

Time Complexity\mathtt{Time} \text{ } \mathtt{Complexity}

O(p+2tlog(n+m))\operatorname{O}(p + 2^t \log (n + m))

Code\mathtt{Code}

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2022-05-04 10:55:29 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2022-05-04 11:12:58
 */
#include <iostream>
#include <cstdio>

#define int long long

inline int read() {
    int x = 0;
    bool flag = true;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            flag = false;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 1) + (x << 3) + ch - '0';
        ch = getchar();
    }
    if(flag) return x;
    return ~(x - 1);
}

const int maxp = 1e5 + 5;
const int maxt = 20;
int b[maxt];
int n, m, t, p;

inline int pow(int a, int b = p - 2LL) {
    int s = 1;
    for (; b; b >>= 1, (a *= a) %= p)
        if (b & 1)
            (s *= a) %= p;
    return s;
}

int fac[maxp], inv[maxp];

inline int C(int n, int m) {
    if (n < m)
        return 0;
    return fac[n] * inv[n - m] % p * inv[m] % p;
}

inline int lucas(int n, int m) {
    int ans = 1;
    for (; m; n /= p, m /= p)
        (ans *= C(n % p, m % p)) %= p;
    return ans;
}

signed main() {
    n = read();
    t = read();
    m = read();
    p = read();
    for (int i = 1; i <= t; ++i)
        b[i] = read();

    fac[0] = inv[0] = 1;
    for (int i = 1; i < p; ++i)
        fac[i] = fac[i - 1] * i % p;
    inv[p - 1] = pow(fac[p - 1]);
    for (int i = p - 2; i; --i)
        inv[i] = inv[i + 1] * (i + 1) % p;

    int ans = 0;
    for (int i = 0; i < (1 << t); ++i) {
        int cnt = 0, k = m;
        for (int j = 1; j <= t; ++j)
            if (i & (1 << j - 1)) {
                ++cnt;
                k -= b[j] + 1;
            }
        int g = lucas(n + k, n);
        (ans += (cnt & 1) ? p - g : g) %= p;
    }

    printf("%lld\n", ans);
    return 0;
}

推荐!

posted @   dbxxx  阅读(53)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示