P2183 [国家集训队]礼物(扩展卢卡斯)

Posted Jozky86

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P2183 [国家集训队]礼物(扩展卢卡斯)相关的知识,希望对你有一定的参考价值。

P2183 [国家集训队]礼物

题意:

有n个礼物,分给m个人,分给第i个人的礼物数量是wi,问送礼物的方案数。

题解:

扩展卢卡斯模板题
很容易看出和组合数有关的题目,对于总方案,完美可以将其分解为m个不同的方案数的乘积
比如样例1:4个礼物,2个人,第一个人要1个礼物,则第一个人取走礼物的方案为 C 4 1 C_{4}^{1} C41,第二个人要2个礼物,方案为 C 3 2 C_{3}^{2} C32,总方案为 C 4 1 ∗ C 3 2 = 12 C_{4}^{1}*C_{3}^{2}=12 C41C32=12
现在改变顺序:第二个人先拿礼物, C 4 2 C_{4}^{2} C42,然后第一个人拿礼物, C 2 1 C_{2}^{1} C21,总方案为 C 4 2 ∗ C 2 1 = 12 C_{4}^{2}*C_{2}^{1}=12 C42C21=12
完美可以发现,无论谁取走礼物,结果都是一样的,那我们只需要按照所给礼物顺序计算就行。
总方案为: C n w 1 ∗ C n − w 1 w 2 ∗ . . . .   m o d   p C_{n}^{w_{1}}*C_{n-w_{1}}^{w_{2}}*....\\bmod p Cnw1Cnw1w2....modp
本题的p不一定是质数,此时就没办法求组合数,就要用的扩展卢卡斯定理,该算法就是专门解决模数p不是质数的情况

代码:

#include <bits/stdc++.h>
#include <cstdio>
using namespace std;
#define FOR(i, a, b) for (ll i= a; i <= b; ++i)
#define DEC(i, a, b) for (ll i= a; i >= b; --i)

typedef long long ll;

ll mod;

void exgcd(ll a, ll b, ll& x, ll& y)
{
    if (!b) {
        x= 1, y= 0;
        return;
    }
    exgcd(b, a % b, y, x);
    y-= a / b * x;
    return;
}

inline ll inv(ll n, ll p)
{
    ll x, y;
    exgcd(n, p, x, y);
    return (x + p) % p;
}

ll qpow(ll base, ll p, ll mod)
{
    ll ret= 1;
    for (; p; p>>= 1, base= base * base % mod)
        if (p & 1)
            ret= ret * base % mod;
    return ret;
}

ll CRT(int n, ll* a, ll* m)
{
    ll M= 1, ret= 0;
    FOR(i, 1, n) M*= m[i];
    FOR(i, 1, n)
    {
        ll w= M / m[i];
        ret= (ret + a[i] * w % mod * inv(w, m[i]) % mod) % mod;
    }
    return (ret + mod) % mod;
}

ll calc(ll n, ll q, ll qk)
{
    if (!n)
        return 1;
    ll ret= 1;
    FOR(i, 1, qk)
    if (i % q)
        ret= ret * i % qk;
    ret= qpow(ret, n / qk, qk);
    FOR(i, n / qk * qk + 1, n)
    if (i % q)
        ret= ret * (i % qk) % qk;
    return ret * calc(n / q, q, qk) % qk;
}

ll multiLucas(ll n, ll m, ll q, ll qk)
{
    int cnt= 0;
    for (ll i= n; i; i/= q)
        cnt+= i / q;
    for (ll i= m; i; i/= q)
        cnt-= i / q;
    for (ll i= n - m; i; i/= q)
        cnt-= i / q;
    return qpow(q, cnt, qk) * calc(n, q, qk) % qk * inv(calc(m, q, qk), qk) % qk * inv(calc(n - m, q, qk), qk) % qk;
}

ll exLucas(ll n, ll m, ll p)
{
    int cnt= 0;
    ll qk[20], a[20]; //存放所有的 q^k 和待合并答案的结果
    for (ll i= 2; i * i <= p; ++i) //质因数分解
    {
        if (p % i == 0) {
            qk[++cnt]= 1;
            while (p % i == 0)
                qk[cnt]*= i, p/= i;
            a[cnt]= multiLucas(n, m, i, qk[cnt]);
        }
    }
    if (p > 1)
        qk[++cnt]= p, a[cnt]= multiLucas(n, m, p, p);
    return CRT(cnt, a, qk); //CRT 合并答案
}
int w[20];
int main()
{
    ll n, m, p;
    scanf("%lld %lld %lld", &p, &n, &m);
    mod= p;
    int sum= 0;
    for (int i= 1; i <= m; i++)
        cin >> w[i], sum+= w[i];
    if (sum > n) {
        printf("Impossible");
        return 0;
    }
    ll ans= 1;
    for (int i= 1; i <= m; i++) {
        ans= ans * exLucas(n, w[i], mod) % mod;
        n-= w[i];
    }
    printf("%lld\\n", ans);
    return 0;
}

以上是关于P2183 [国家集训队]礼物(扩展卢卡斯)的主要内容,如果未能解决你的问题,请参考以下文章

bzoj2142: 礼物

Luogu2183国家集训队礼物

bzoj 3129

P4720 模板扩展卢卡斯定理/exLucas(无讲解,纯记录模板)

算法卢卡斯定理

扩展卢卡斯定理