[JLoi 2016] bzoj4559 成绩比较 [容斥原理]

本文介绍了一种解决特定计数问题的方法,该问题涉及学生在课程上的成绩分布情况。通过两个主要步骤——确定成绩排名的可能性及计算具体分数的组合数量,来找出既满足特定条件又符合已知排名的所有可能情况。

Description:
GG系共有n位同学,MM门必修课。这N位同学的编号为00N1的整数,其中B神的编号为00号。这M门必修课编号为0M1M−1的整数。一位同学在必修课上可以获得的分数是11Ui中的一个整数。如果在每门课上AA获得的成绩均小于等于B获得的成绩,则称ABB碾压。在B神的说法中,GG系共有K位同学被他碾压(不包括他自己),而其他NK1N−K−1位同学则没有被他碾压。DD神查到了B神每门必修课的排名。这里的排名是指:如果B神某门课的排名为RR,则表示有且仅有R-1位同学这门课的分数大于B神的分数,有且仅有NRN−R位同学这门课的分数小于等于BB神(不包括他自己)。我们需要求出全系所有同学每门必修课得分的情况数,使其既能满足B神的说法,也能符合DD神查到的排名。这里两种情况不同当且仅当有任意一位同学在任意一门课上获得的分数不同。你不需要像D神那么厉害,你只需要计算出情况数模109+7109+7的余数就可以了。


Solution:
需要计算两个部分,两个部分乘起来即是答案。
第一个部分是碾压kk个人且满足排名的情况下所有人的排名与B的排名的大小关系的方案数。第二个部分则是每个人分数满足条件的方案数,两个部分乘起来即是答案,因为每种大小关系都可以映射到对应的分数。
第一部分容斥计算,恰好kk个转化为至少k个是常用技巧。用f[i]f[i]表示至少碾压i个人的方案数,那么f[i]=Cin1mj=1Crnk[j]ni1f[i]=Cn−1i∗∏j=1mCn−i−1rnk[j],所以ans1=n1i=kCki(1)ikf[i]ans1=∑i=kn−1Cik∗(−1)i−k∗f[i]
第二部分考虑枚举BB的分数x,那么对于一个xx方案数是xnrnk(ux)rnk1
ans2=mi=1u[i]x=1xnrnk[i](ux)rnk[i]1ans2=∑i=1m∑x=1u[i]xn−rnk[i]∗(u−x)rnk[i]−1
二项式定理展开(ux)rnk[i]1(u−x)rnk[i]−1,那么对于每门课的方案数是
ux=1rnk1i=1uiCirnk1xni1(1)rnki1∑x=1u∑i=1rnk−1ui∗Crnk−1i∗xn−i−1∗(−1)rnk−i−1
交换求和顺序
rnk1i=1uiCirnk1(1)rnki1ux=1xni1∑i=1rnk−1ui∗Crnk−1i∗(−1)rnk−i−1∗∑x=1uxn−i−1
预处理伯努利数求ux=1xni1∑x=1uxn−i−1


#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 205, P = 1e9 + 7;
int n, m, k;
ll ans1, ans2 = 1;
int u[N], rnk[N];
ll c[N][N], s[N][N], inv[N], b[N];
ll power(ll x, ll t) {
    ll ret = 1;
    for(; t; t >>= 1, x = x * x % P) {
        if(t & 1) {
            ret = ret * x % P;
        }
    }
    return ret;
}
ll calc(int n, int k) {
    ll fac = 1, ans = 0;
    for(int i = 1; i <= k + 1; ++i) 
    {
        fac = fac * (n + 1) % P;
        ans = (ans + c[k + 1][i] * b[k + 1 - i] % P * fac % P) % P;
    }
    ans = ans * inv[k + 1] % P;
    return ans;    
}
int main() {
    scanf("%d%d%d", &n, &m, &k);
    c[0][0] = 1;
    s[0][0] = 1;
    for(int i = 1; i <= 200; ++i) {
        c[i][0] = 1;
        for(int j = 1; j <= i; ++j) {
            c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % P;
        }
    }
    for(int i = 1; i <= m; ++i) {
        scanf("%d", &u[i]);
    }
    for(int i = 1; i <= m; ++i) {
        scanf("%d", &rnk[i]);
    }
    inv[1] = 1;
    for(int i = 1; i < N; ++i) {
        if(i != 1) inv[i] = (P - P / i) * inv[P % i] % P;
    }
    b[0] = 1;
    for(int i = 1; i < N - 1; ++i) {
        for(int j = 0; j < i; ++j) {
            b[i] = (b[i] + c[i + 1][j] * b[j]) % P;
        }
        b[i] = ((b[i] * -inv[i + 1] % P) + P) % P;
    }
    for(int i = k; i < n; ++i) {
        ll tmp = c[i][k] * (((i - k) & 1) ? -1 : 1);
        tmp = (tmp % P + P) % P;
        for(int j = 1; j <= m; ++j) {
            tmp = tmp * c[n - i - 1][rnk[j] - 1] % P;
        }
        ans1 = (ans1 + tmp * c[n - 1][i] % P) % P;
    }
    for(int i = 1; i <= m; ++i) {
        ll tmp = 0, pw = 1;
        for(int j = 0; j < rnk[i]; ++j, pw = pw * u[i] % P) {
            tmp = (tmp + power(u[i], j) * c[rnk[i] - 1][j] % P * calc(u[i], n - j - 1) * (((rnk[i] - j - 1) & 1) ? -1 : 1) % P) % P;
            tmp = (tmp + P) % P;
        }
        ans2 = ans2 * tmp % P;
    }
    printf("%lld\n", ans1 * ans2 % P);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值