[BZOJ4830][HNOI2017]抛硬币-扩展Lucas定理

抛硬币

Description

小A和小B是一对好朋友,他们经常一起愉快的玩耍。最近小B沉迷于**师手游,天天刷本,根本无心搞学习。但是已经入坑了几个月,却一次都没有抽到SSR,让他非常怀疑人生。勤勉的小A为了劝说小B早日脱坑,认真学习,决定以抛硬币的形式让小B明白他是一个彻彻底底的非洲人,从而对这个游戏绝望。两个人同时抛b次硬币,如果小A的正面朝上的次数大于小B正面朝上的次数,则小A获胜。

但事实上,小A也曾经沉迷过拉拉游戏,而且他一次UR也没有抽到过,所以他对于自己的运气也没有太大把握。所以他决定在小B没注意的时候作弊,悄悄地多抛几次硬币,当然,为了不让小B怀疑,他不会抛太多次。现在小A想问你,在多少种可能的情况下,他能够胜过小B呢?由于答案可能太大,所以你只需要输出答案在十进制表示下的最后k位即可。

Input

有多组数据,对于每组数据输入三个数a,b,k,分别代表小A抛硬币的次数,小B抛硬币的次数,以及最终答案保留多少位整数。
1≤a,b≤10^15,b≤a≤b+10000,1≤k≤9,数据组数小于等于10。

Output

对于每组数据,输出一个数,表示最终答案的最后k位为多少,若不足k位以0补全。

Sample Input

2 1 9
3 2 1

Sample Output

000000004
6

【样例解释】

对于第一组数据,当小A抛2次硬币,小B抛1次硬币时,共有4种方案使得小A正面朝上的次数比小B多。(01,0),(10,0),(11,0),(11,1)

对于第二组数据,当小A抛3次硬币,小B抛2次硬币时,共有16种方案使得小A正面朝上的次数比小B多。(001,00),(010,00),(100,00),(011,00),(101,00),(110,00),(111,00),(011,01),(101,01),(110,01),(111,01),(011,10),(101,10),(110,10),(111,10),(111,11)


壮哉我大数学……
像咱这种蒟蒻只能乖乖敲暴力……
(人家大佬都被坑了你一个蒟蒻还在这跳什么)


思路:
想必大家都想到了暴力的方法吧……
正解是一种神奇的东西:

先从特殊情况入手:a=b
假设他们的各种情况构成了一个序列 a+b。
这里咱发现对于每一种小B赢了的情况,都对应的有一个小A赢了的情况,即整个序列的每一位异或上1,就变成小A赢了……
但这不可避免的,每种状态被计算了两次。
于是得到公式:

ans(a,a)=22a(2aa)2

这就是说,对于所有可能的序列,减去小A正面朝上次数等于小B正面朝上次数的情况,再除以二排除重复。
为什么要减呢?因为相等时翻转了小A还是会输……

对于其他情况,咱发现公式差不多:

ans(a,b)=2a+b+F2

其中F代表翻转以后还是小A赢的情况。
为什么用加F而不用之前的减呢?
因为保证a>=b,在翻转后有些情况小A可以靠总量碾压……

那么我们来推一推F怎么算:

F=bi=0(bi)ab1j=1(ai+j)=bi=0ab1j=1(bbi)(ai+j)

考虑到在a+b中选b+j的一个方案对应着一组(b-i,i+j)的拆分,得到

F=ab1i=1(a+bb+i)

于是公式都推完了~
然而,离结束还早呢……

组合数方面,我们可以用扩展Lucas定理去求。
然后就会T……

观察,发现模数质因子仅有2和5,于是简化中国剩余定理部分至无循环。
预处理对于最大位的模数,不整除2和5的所有数的乘积。
即扩展Lucas中的当前层递归要处理的部分。

又发现对于2的快速幂处理部分,无论如何每一层递归都只有1的贡献。
如果是5的快速幂,若为奇数次幂还要多算一个当前位数模数的贡献。
于是递归简化为两个while循环,每一次循环必乘一次当前模拟的递归层的贡献,5的情况特判。

最后,卡常。
——就可以AC了!
真不容易啊( ̄▽ ̄)/

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

typedef long long ll;

ll md,ans,a,b,k;

inline ll qpow(ll a,ll b,ll p)
{
    ll ret=1;
    while(b)
    {
        if(b&1ll)
            ret=ret*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ret;
}

const ll max2=1024,max5=1953125,max10=2000000000,base2=212890625,base5=1787109376;

ll f[max2+3],ff[max5+3];

inline void init()
{
    f[0]=1;ff[0]=1;
    for(ll i=1;i<=max2;i++)
    {
        f[i]=f[i-1];
        if(i&1)
            f[i]=f[i]*i%max2;
    }
    for(ll i=1;i<=max5;i++)
    {
        ff[i]=ff[i-1];
        if(i%5)
            ff[i]=ff[i]*i%max5;
    }
}

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

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

ll calc(ll n,ll a,ll p)//n len
{
    if(!n)
        return 1;

    ll ret=1,t=n/p;
    if(a==2)
        ret=1;
    else
        ret=t&1?ff[p]:1;

    for(ll i=t*p+1;i<=n;i++)
        if(i%a)
            ret=ret*i%p;

    return ret*calc(n/a,a,p)%p;
}

inline ll calc2(ll n)
{
    ll ret=1;
    while(n)
    {
        ret=ret*f[n%max2]%max2;
        n>>=1;
    }
    return ret;
}

inline ll calc5(ll n)
{
    ll ret=1;
    while(n)
    {
        if((n/max5)&1)
            ret=ret*ff[max5-1]%max5;
        ret=ret*ff[n%max5]%max5;
        n/=5;
    }
    return ret;
}

inline ll lucas2(ll n,ll m,ll p)
{
    ll cnt=0;
    for(ll i=n;i;i>>=1)
        cnt+=i/2;
    for(ll i=m;i;i>>=1)
        cnt-=i/2;
    for(ll i=n-m;i;i>>=1)
        cnt-=i/2;

    return qpow(2,cnt,p)
        *calc2(n)%p
        *inv(calc2(m),p)%p
        *inv(calc2(n-m),p)%p;
}

inline ll lucas5(ll n,ll m,ll p)
{
    ll cnt=0;
    for(ll i=n;i;i/=5)
        cnt+=i/5;
    for(ll i=m;i;i/=5)
        cnt-=i/5;
    for(ll i=n-m;i;i/=5)
        cnt-=i/5;

    return qpow(5,cnt,p)
        *calc5(n)%p
        *inv(calc5(m),p)%p
        *inv(calc5(n-m),p)%p;
}

inline ll crt(ll a,ll b)
{
    return (base5*lucas5(a,b,max5)%max10+base2*lucas2(a,b,max2)%max10)%max10;
}

int main()
{
    init();
    while(~scanf("%lld%lld%lld",&a,&b,&k))
    {
        md=pow(10,k)*2;

        if(a==b)
            ans=(qpow(2,a<<1,md)-crt(a<<1,a)+md)%md;
        else
        {
            ans=qpow(2,a+b,md);
            for(ll i=1;i<a-b;i++)
                ans=(ans+crt(a+b,b+i))%md;
        }
        ans/=2;

        while(ans<md/20)
            putchar('0'),md/=10;
        if(ans>0)
            printf("%lld\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值