[HNOI2017]抛硬币(组合数学,扩展lucas)

Description

小A和小B抛硬币,小A抛a次,小B抛b次,求小A赢过小B的方案数。

Solution

这道题 ab a − b 较小,所以可以考虑关于 ab a − b 的式子。

  • a=b a = b ,若 a=b a = b ,则唯一需要考虑的平局的情况(因为情况是对称的)。
    s s 表示平局的方案数,则

    s=i=0aCiaCia=i=0aCaiaCia=Ca2a s = ∑ i = 0 a C a i C a i = ∑ i = 0 a C a a − i C a i = C 2 a a

    ans=2a+bs2 a n s = 2 a + b − s 2

  • a>b a > b ,则无论如何也有一种情况使得小A赢,则只需要加上两种对称情况小A都赢的次数 s s
    设小B赢了 i i 次,小A赢了 i+j i + j 次。

    a(i+j)>bijab1 a − ( i + j ) > b − i ⇒ j ⩽ a − b − 1

    s=i=0bj=1j=ab1CibCai+j=i=0bj=1j=ab1CibCi+ja=i=0bj=1j=ab1CbibCi+ja=j=1ab1Cb+ja+b s = ∑ i = 0 b ∑ j = 1 j = a − b − 1 C b i C a i + j = ∑ i = 0 b ∑ j = 1 j = a − b − 1 C b i C a i + j = ∑ i = 0 b ∑ j = 1 j = a − b − 1 C b b − i C a i + j = ∑ j = 1 a − b − 1 C a + b b + j

    然后用扩展lucas搞一搞就行了。

#include 
using namespace std;

const int maxn = 10005, mod = (int)1e9;
typedef long long lint;

int pow(int x, lint k, int mod)
{
    int ret = 1;
    while (k) {
        if (k & 1) ret = (lint)ret * x % mod;
        x = (lint)x * x % mod; k >>= 1;
    }
    return ret;
}

const int p1 = 512, p2 = 1953125, inv1 = 109, inv2 = 1537323, inv3 = 976563;
int fac1[p1 + 5], fac2[p2 + 5];

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

int inv(int x, int p)
{
    lint a, b;
    exgcd(x, p, a, b);
    a = (a % p + p) % p;
    return a;
}

void prepare()
{
    fac1[0] = 1;
    for (int i = 1; i <= p1; ++i) fac1[i] = (lint)(i % 2 ? i : 1) * fac1[i - 1] % p1;
    fac2[0] = 1;
    for (int i = 1; i <= p2; ++i) fac2[i] = (lint)(i % 5 ? i : 1) * fac2[i - 1] % p2;
}

int fact1(lint n)
{
    if (n <= 0) return 1;
    return (lint)pow(fac1[p1], n / p1, p1) * fac1[n % p1] % p1 * fact1(n / 2) % p1;
}

int C1(lint n, lint m, bool type)
{
    int A = fact1(n), B = fact1(n - m), C = fact1(m), cnt = 0;
    for (lint x = n; x; x >>= 1) cnt += x / 2;
    for (lint x = n - m; x; x >>= 1) cnt -= x / 2;
    for (lint x = m; x; x >>= 1) cnt -= x / 2;
    if (type) return (lint)A * inv(B, p1) % p1 * inv(C, p1) % p1 * pow(2, cnt, p1) % p1;
    else return (lint)A * inv(B, p1) % p1 * inv(C, p1) % p1 * pow(2, cnt - 1, p1) % p1;
}

int fact2(lint n)
{
    if (n <= 0) return 1;
    return (lint)pow(fac2[p2], n / p2, p2) * fac2[n % p2] % p2 * fact2(n / 5) % p2;
}

int C2(lint n, lint m, bool type)
{
    int A = fact2(n), B = fact2(n - m), C = fact2(m), cnt = 0;
    for (lint x = n; x; x /= 5) cnt += x / 5;
    for (lint x = n - m; x; x /= 5) cnt -= x / 5;
    for (lint x = m; x; x /= 5) cnt -= x / 5;
    if (type) return (lint)A * inv(B, p2) % p2 * inv(C, p2) % p2 * pow(5, cnt, p2) % p2;
    else return (lint)A * inv(B, p2) % p2 * inv(C, p2) % p2 * pow(5, cnt, p2) % p2 * inv3 % p2;
}

int C(lint n, lint m, bool type)
{
    int A = C1(n, m, type), B = C2(n, m, type);
    A = (lint)(A * p2 % mod) * inv1 % mod;
    B = (lint)(B * p1 % mod) * inv2 % mod;
    return (A + B) % mod;
}

void print(int x, int k)
{
    int a[15] = {0}, cnt = 0;
    while (x) a[++cnt] = x % 10, x /= 10;
    for (int i = k; i >= 1; --i) putchar(a[i] + '0');
    puts("");
}

int main()
{
    freopen("coin.in", "r", stdin);
    freopen("coin.out", "w", stdout);

    prepare();

    lint a, b, k;
    while (scanf("%lld%lld%lld", &a, &b, &k) == 3) {
        if (a == b) print(((pow(2, a + b - 1, mod) - C(a + b, a, 0)) % mod + mod) % mod, k);
        else {
            lint ans = pow(2, a + b - 1, mod);
            if (((a + b) & 1) == 0) {
                ans += C(a + b, (a + b) / 2, 0);
                if (ans >= mod) ans -= mod;
            }
            for (lint i = (a + b) / 2 + 1; i <= a - 1; ++i) {
                ans += C(a + b, i, 1);
                if (ans >= mod) ans -= mod;
            }
            print(ans, k);
        }
    }

    return 0;
}

你可能感兴趣的:([HNOI2017]抛硬币(组合数学,扩展lucas))