[BZOJ4559][JLoi2016]成绩比较(容斥+组合数学+拉格朗日插值)

Address

https://www.lydsy.com/JudgeOnline/problem.php?id=4559

Solution

由容斥原理得:

ans=i=Kmin{N1R}(1)iKCKiCiN1j=1MCNRjiN1ix=1UixNRj(Ujx)Rj1 a n s = ∑ i = K min { N − 1 − R } ( − 1 ) i − K C i K C N − 1 i ∏ j = 1 M C N − 1 − i N − R j − i ∑ x = 1 U i x N − R j ( U j − x ) R j − 1

这么长的式子,一项一项地进行解析:
(1) min{N1R}i=K(1)iKCKi ∑ i = K min { N − 1 − R } ( − 1 ) i − K C i K :求「恰好完虐 K K 个同学」时乘上容斥系数。
(2) CN1i :在 N1 N − 1 个同学中选出 i i 个同学被完虐。
(3) j=1M :把每一科分开处理之后相乘。
(4) CNRjiN1i C N − 1 − i N − R j − i :在没有被完虐的 N1i N − 1 − i 个同学中,选出 NRji N − R j − i 个同学,让他们第 j j 课被虐。
(5) x=1Ui :枚举 B 神第 j j 科成绩。
(6) xNRj :第 j j 科被虐的同学的成绩方案数。
(7) (Ujx)Rj1 :第 j j 科没有被虐的同学的方案数。
显然,我们要处理的关键在于 x=1Ui 及之后的式子。
发现 (Ujx)Rj1 ( U j − x ) R j − 1 不好处理。
考虑无脑展开:
x=1UixNRjk=0Rj1(1)kxkURj1kj ∑ x = 1 U i x N − R j ∑ k = 0 R j − 1 ( − 1 ) k x k U j R j − 1 − k

=k=0Rj1(1)kURj1kjx=1UixNRj+k = ∑ k = 0 R j − 1 ( − 1 ) k U j R j − 1 − k ∑ x = 1 U i x N − R j + k

注意到 Uix=1xNRj+k ∑ x = 1 U i x N − R j + k 是以 x x 为自变量的 NRj+k+1 次多项式,上拉格朗日插值法即可求得。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 111, ZZQ = 1e9 + 7;
int pwe[N][N], y[N], qaq[N], tmp[N], rps[N], inv[N], com[N][N],
C[N][N], n, m, K, U[N], R[N], ans, czk[N];
int qpow(int a, int b) {
    int res = 1;
    while (b) {
        if (b & 1) res = 1ll * res * a % ZZQ;
        a = 1ll * a * a % ZZQ;
        b >>= 1;
    }
    return res;
}
void lagrange(int n) {
    int i, j;
    For (i, 1, n + 2)
        y[i] = (y[i - 1] + pwe[i][n]) % ZZQ;
    qaq[0] = 1;
    For (i, 1, n + 2) {
        tmp[0] = qaq[i] = 0;
        For (j, 0, i - 1) tmp[j + 1] = qaq[j];
        For (j, 0, i)
            qaq[j] = (tmp[j] - 1ll * qaq[j] * i % ZZQ + ZZQ) % ZZQ;
    }
    For (i, 1, n + 2) {
        For (j, 0, n + 2) tmp[j] = qaq[j];
        Rof (j, n + 1, 0) rps[j] = tmp[j + 1],
            tmp[j] = (tmp[j] + 1ll * tmp[j + 1] * i % ZZQ) % ZZQ;
        int sp = 1;
        For (j, 1, i - 1) sp = 1ll * sp * inv[j] % ZZQ;
        For (j, 1, n - i + 2)
            sp = 1ll * sp * (ZZQ - inv[j]) % ZZQ;
        For (j, 0, n + 1)
            com[n][j] = (com[n][j] + 1ll * y[i] *
            rps[j] % ZZQ * sp % ZZQ) % ZZQ;
    }
}
void init() {
    int i, j; inv[1] = 1;
    For (i, 2, 101) inv[i] = 1ll * (ZZQ - ZZQ / i) * inv[ZZQ % i] % ZZQ;
    For (i, 0, 102) {
        pwe[i][0] = 1;
        For (j, 1, 100) pwe[i][j] = 1ll * pwe[i][j - 1] * i % ZZQ;
    }
    For (i, 0, 100) lagrange(i);
    For (i, 0, 100) C[i][0] = 1;
    For (i, 1, 100) For (j, 1, i)
        C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % ZZQ;
}
int getval(int n, int k) {
    int i, x = 1, res = 0;
    For (i, 0, k + 1) res = (res + 1ll * com[k][i] * x % ZZQ) % ZZQ,
        x = 1ll * x * n % ZZQ;
    return res;
}
int main() {
    int i, j, k, dalao;
    init();
    dalao = n = read(); m = read(); K = read();
    For (i, 1, m) U[i] = read();
    For (i, 1, m) R[i] = read();
    For (i, 1, m) dalao = min(dalao, n - R[i]);
    For (j, 1, m) For (k, 0, R[j] - 1) {
        int miu = k & 1 ? ZZQ - 1 : 1, delta;
        delta = 1ll * C[R[j] - 1][k] *
            qpow(U[j], R[j] - 1 - k) % ZZQ
            * getval(U[j], n + k - R[j]) % ZZQ;
        czk[j] = (czk[j] + 1ll * miu * delta % ZZQ) % ZZQ;
    }
    For (i, K, dalao) {
        int miu = i - K & 1 ? ZZQ - 1 : 1,
            delta = 1ll * C[n - 1][i] * C[i][K] % ZZQ, pyz = 1;
        For (j, 1, m)
            pyz = 1ll * pyz * C[n - 1 - i][n - R[j] - i] % ZZQ,
            pyz = 1ll * pyz * czk[j] % ZZQ;
        ans = (ans + 1ll * miu * delta % ZZQ * pyz % ZZQ) % ZZQ;
    }
    cout << ans << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值