[容斥+lucas] LightOJ 1124 - Cricket Ranking

来源:互联网 发布:程序员一般用什么键盘 编辑:程序博客网 时间:2024/05/16 00:47

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());    }}



0 0