[容斥+lucas] LightOJ 1124 - Cricket Ranking

博客探讨了如何解决LightOJ 1124题——板球排名问题。通过将问题转化为求解多个区间内整数和为特定值的方案数,利用容斥原理和Lucas定理进行求解。文章详细解释了如何从无限多重集合到有限多重集合的转换,以及如何计算超过区间上限的非法状态,最后使用二进制枚举非法状态来解决这个问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LightOJ 1124 - Cricket Ranking

题意:有k个区间[l, r],从每个区间里取一个整数,问k个数和为n的方法数。

题解显然的,题目可以转化成从k个区间[0, r[i]-l[i]] (1<=i<=k)里取k个数和为s=n-sum(l)的方法数,并令v[i] = r[i]-l[i]。

可以理解成x1+x2+x3+...xn = s的非负整数解的个数,其中xi∈[0, v[i]]。

A*表示我们需要求的多重集合,A*={v[1]*a1, v[2]*a2, ..., v[n]*an},认为a1=a2=a3=..an=1即可,假如选了ri(0<=r<=v[i])个ai,那么xi的值就是ri,并且有r1+r2+r3+...+rn = s,这就是多重集合的s组合和这个方程之间的关系

显然,答案就是A*的s组合的个数。

先不考虑上限,即每个数可能超过v[i],也可能没有超过,这个新集合可以表示为A={∞*a1, ∞*a2, ..., ∞*an}。

那么新集合A的s组合个数的计算方法,是个经典的n种元素无限重数的多重集合的s组合问题,这个结果为|As|=C(s+n-1, n-1),As表示A的所有s组合。

然后应用容斥原理。

我们对于i=1,2,3...,用Pi表示第i个数超过v[i]的性质,对于i=1,2,3....用Ai表示As中具有Pi性质的组合的集合,也就是Ai表示第i个盒子不合法的情况的集合。

如果用!Ai表示Ai的补集,那么要求的答案ans=|!A1∩!A2∩!A3∩....∩!An|。

根据集合知识,ans = |A| - |(A1∪A2∪A3∪...∪An)|

由于其他的类似,我们来讨论|A1|如何计算。

显然A1是A中r1>=v[1]+1的那些组合组成的。

如果从A1中取出任意一个元素,并减去v[1]+1个x1,那么我们就得到A的s-v[1]-1组合的一个元素。

如果从A的s-v[1]-1组合中取出任意一个元素,并加上v[1]+1个x1,那么就得到A1的一个元素。

容易证明|A1|=|A的s-v[1]-1组合|,这个组合的计算前面说过了,等于C( (s-v[1]-1)+n-1, n-1)。

后面多个集合的并的计算方法就是容斥公式和lucas组合数+逆元快速幂了。

用二进制枚举任意非法状态。

#include<cstdio>
using namespace std;
typedef long long ll;
const int mod = 100000007;
ll n, s;
ll v[15];
ll qpow(ll a, ll k){
    ll res = 1;
    while(k){
        if(k&1) { res *= a; if(res >= mod) res %= mod; }
        a *= a; if(a >= mod) a %= mod;
        k >>= 1;
    }
    return res;
}
ll C(ll a, ll b){
    if(a < b) return 0;
    if(b > a-b) b = a-b;
    ll up = 1, down = 1;
    for(ll i = 0; i < b; ++i){
        up *= (a-i); if(up >= mod) up %= mod;
        down *= (i+1); if(down >= mod) down %= mod;
    }
    return up*qpow(down, mod-2)%mod;
}
ll lucas(ll a, ll b){
    ll res = 1;
    while(b){
        res *= C(a%mod, b%mod);
        if(res >= mod) res %= mod;
        a/= mod, b /= mod;
    }
    return res;
}
ll solve(){
    ll ans = lucas(s+n-1, n-1); // 全集
    ll mx = 1<<n;
    int f[2] = {1, -1};
    for(int i = 1; i < mx; ++i){
        int flag = 0; ll tmp = s;
        for(int j = 0; j < n; ++j){
            if(i&(1<<j)){
                tmp -= v[j]+1;
                flag += 1;
            }
        }
        if(tmp < 0) continue;
        ans += f[flag&1]*lucas(tmp+n-1,n-1); // 容斥公式
        if(ans < 0) ans += mod;
        else if(ans >= mod) ans %= mod;
    }
    return ans;
}
int main(){
    int T, ca = 1;
    scanf("%d", &T);
    while(T--){
        ll sum = 0;
        scanf("%lld%lld", &n, &s);
        for(int i = 0; i < n; ++i){
            ll l, r;
            scanf("%lld%lld", &l, &r);
            r -= l, s -= l, v[i] = r;
        }
        printf("Case %d: %lld\n", ca++, solve());
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值