JZOJ3851. 发奖金(reward)

题目大意

m 元与n个员工,将这 m 元钱当做奖金发给每一个员工,钱可不全部发完,而且可能有员工没有奖金,求可能的方案数。最后答案%p
以m=1, n=2为例:
方案1:员工1发1元,员工2发0元
方案2:员工1发0元,员工2发1元
方案3:员工1发0元,员工2发0元

Data Constraint

对于p:设p=pc11pc22pc33pctt , pi 为质数。
20%的数据: 1n,m15
40%的数据: 1n,m1000p=10007
60%的数据:保证 t=1ci=1pcii105
80%的数据: t2ci=1pi105
100%的数据: 1n,m1091pcii105 ,所有P不超过 2311

题解

考虑插板法:
可以将奖金视为 m 个小球,由于老板可以自己留钱,所以可以看为n+1个人分 m 元。由挡板原理易得最终方案数为Cnm+n
但是这题的 p 不一定是质数,所以不能直接求逆元或者Lucas定理。
这里介绍一种组合数取模的方法。

p=pc11pc22pc33pctt
对于每一个 pcii 分开考虑,最后用中国剩余定理合并答案即可。
f[n] 表示不包含 pi 因数的 n!
Cnm+n=(m+n)!m!n!=pkif[m+n]f[m]f[n]=pkif[m+n](f[m]f[n])1
其中 k 表示分子提取出的因数个数与分母之差,即上下约分后剩余的pi
然后 f k都可以递归求出。
补充几点:

  1. f <script type="math/tex" id="MathJax-Element-28">f</script>可以预处理,尤其对于多组数据,这样做能大幅度提高效率。
  2. 求逆元时用欧拉定理比较简单。

SRC

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std ;

typedef long long ll ;

ll Pri[20][3] , A[20] ;
ll n , m , L , P , MO , ans , tot ;

ll Power( ll x , ll k ) {
    ll s = 1 ;
    while ( k ) {
        if ( k % 2 == 1 ) s = s * x % MO ;
        x = x * x % MO ;
        k /= 2 ;
    }
    return s ;
}

ll phi( ll p , ll X ) { return X / p * (p - 1) ; }

ll count( ll n , int p ) { return n == 0 ? 0 : n / p + count( n / p , p ) ; }

ll fac( ll n , int p ) {
    if ( !n ) return 1 ;
    ll ret = 1 ;
    for (int i = 1 ; i <= MO ; i ++ ) {
        if ( i % p == 0 ) continue ;
        ret = ret * i % MO ;
    }
    ret = Power( ret , n / MO ) ;
    for (int i = (n / MO) * MO + 1 ; i <= n ; i ++ ) {
        if ( i % p == 0 ) continue ;
        ret = ret * i % MO ;
    }
    return ret * fac( n / p , p ) % MO ;
}

int main() {
    scanf( "%I64d%I64d%I64d" , &n , &m , &P ) ;
    int up = min( 1e5 , sqrt(P) ) , now = P ;
    for (int i = 2 ; i <= up && now > 1 ; i ++ ) {
        if ( now % i ) continue ;
        Pri[tot+1][2] = 1 ;
        while ( now % i == 0 ) {
            Pri[tot+1][0] ++ ;
            Pri[tot+1][2] *= i ;
            now /= i ;
        }
        Pri[++tot][1] = i ;
    }
    if ( now > 1 ) {
        Pri[++tot][0] = 1 ;
        Pri[tot][1] = now ;
        Pri[tot][2] = now ;
    }
    L = n + m ;
    for (int i = 1 ; i <= tot ; i ++ ) {
        MO = Pri[i][2] ;
        ll k = count( L , Pri[i][1] ) - count( n , Pri[i][1] ) - count( m , Pri[i][1] ) ;
        A[i] = Power( Pri[i][1] , k ) * fac( L , Pri[i][1] ) % MO ;
        A[i] = A[i] * Power( fac( n , Pri[i][1] ) * fac( m , Pri[i][1] ) % MO , phi( Pri[i][1] , Pri[i][2] ) - 1 ) % MO ;
        ans = (ans + A[i] * (P / MO) % P * Power( P / MO , phi( Pri[i][1] , Pri[i][2] ) - 1 ) % P) % P ;
    }
    printf( "%I64d\n" , ans ) ;
    return 0 ;
}

以上.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值