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

7 篇文章 0 订阅
6 篇文章 0 订阅

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


Solution:
需要计算两个部分,两个部分乘起来即是答案。
第一个部分是碾压 k k 个人且满足排名的情况下所有人的排名与B的排名的大小关系的方案数。第二个部分则是每个人分数满足条件的方案数,两个部分乘起来即是答案,因为每种大小关系都可以映射到对应的分数。
第一部分容斥计算,恰好 k k 个转化为至少k个是常用技巧。用 f[i] f [ i ] 表示至少碾压i个人的方案数,那么 f[i]=Cin1mj=1Crnk[j]ni1 f [ i ] = C n − 1 i ∗ ∏ j = 1 m C n − i − 1 r n k [ j ] ,所以 ans1=n1i=kCki(1)ikf[i] a n s 1 = ∑ i = k n − 1 C i k ∗ ( − 1 ) i − k ∗ f [ i ]
第二部分考虑枚举 B B 的分数x,那么对于一个 x x 方案数是xnrnk(ux)rnk1
ans2=mi=1u[i]x=1xnrnk[i](ux)rnk[i]1 a n s 2 = ∑ i = 1 m ∑ x = 1 u [ i ] x n − r n k [ i ] ∗ ( u − x ) r n k [ i ] − 1
二项式定理展开 (ux)rnk[i]1 ( u − x ) r n k [ i ] − 1 ,那么对于每门课的方案数是
ux=1rnk1i=1uiCirnk1xni1(1)rnki1 ∑ x = 1 u ∑ i = 1 r n k − 1 u i ∗ C r n k − 1 i ∗ x n − i − 1 ∗ ( − 1 ) r n k − i − 1
交换求和顺序
rnk1i=1uiCirnk1(1)rnki1ux=1xni1 ∑ i = 1 r n k − 1 u i ∗ C r n k − 1 i ∗ ( − 1 ) r n k − i − 1 ∗ ∑ x = 1 u x n − i − 1
预处理伯努利数求 ux=1xni1 ∑ x = 1 u x n − 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值