BZOJ 4830: [AH2017/HNOI2017]抛硬币 exLucas

title

BZOJ 4830

LUOGU 3726

简化题意:

两人抛硬币,小 \(A\) 抛硬币的次数为 \(a\) ,小 \(B\) 抛硬币的次数为 \(b\) ,在多少种可能的情况下,他能够胜过小 \(B\) 呢?只需要输出答案在十进制表示下的最后 \(k\) 位即可。

analysis

首先,我们考虑一下 \(a=b\) 的情况。

那么对于一种 \(B\) 获胜的方案序列(比如二人都抛 \(3\) 次,\(A\) 第三次正面朝上,\(B\) 第一次和第二次正面朝上,序列就是 001110),将每一位都异或 \(1\) ,就变成了 \(A\) 获胜的。

当然,还要减去平局(每一种平局将后半部分反序,一共有 \(a\)\(1\) ,所以平局数是 \(C_{a+b}^a\) )。

所以最终答案就是:
\[ \frac{2^{a+b}-C^a_{a+b}}{2}=\frac{2^{2a}-C^a_{2a}}{2} \]


然后考虑 \(a>b\) 的情况( \(a<b\) 的情况,\(A\) 怎么获胜?就不讨论了)。

同样的,将一种 \(B\) 获胜的方案序列的每一位都异或 \(1\) ,就变成了 \(A\) 获胜的。但是现在 \(A\) 抛硬币的次数多啊,可以通过数量取胜,所以就会出现把一种 \(A\) 获胜的方案序列的每一位都异或 \(1\) 后还是 \(A\) 获胜的序列的情况。

这样的序列假设 \(B\) 正面朝上 \(i\) 次, \(A\)\(B\) 多正面朝上 \(j\) 次,就应该满足 \(b−i<a−i−j\) ,种类数就是:
\[ \sum^b_{i=0}\sum^{a-b-1}_{j=1}C^i_bC^{i+j}_{a}=\sum^b_{i=0}\sum^{a-b-1}_{j=1}C^{b-i}_bC^{i+j}_{a} \]

那么我们这么看这个式子:我们从 \(a+b\) 个数组成的序列中选取 \(b+j\) 个元素变成 \(1\) ,然后由于在前 \(b\) 个元素中 \(1\) 的个数就正好可以能完成那个和 \(i\) 有关的枚举,所以上式就等同于:
\[ \sum^{a-b-1}_{i=1}C^{b+i}_{a+b} \]

那么最终答案就是(平局的方案序列取反就能变成获胜序列了,因为 \(A\) 数量多嘛):
\[ \frac{2^{a+b}+\sum^{a-b-1}_{i=1}C^{b+i}_{a+b}}{2} \]

那么组合数的计算就用 \(exLucas\) 吧(模数不一定为质数),而且由于在杨辉三角中组合数具有对称性,所以只需要计算一半组合数就好了,然后卡卡常就好了。

code

#include<bits/stdc++.h>

typedef long long ll;
const int maxn=2e6+10;

namespace IO
{
    char Out[1<<24],*fe=Out;
    inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
    template<typename T>inline void write(T x,char str)
    {
        if (!x) *fe++=48;
        if (x<0) *fe++='-', x=-x;
        T num=0, ch[20];
        while (x) ch[++num]=x%10+48, x/=10;
        while (num) *fe++=ch[num--];
        *fe++=str;
    }
}

using IO::write;

inline ll Quick_power(ll a,ll b,ll mod)
{
    ll ans=1;
    while (b)
    {
        if (b&1) ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans;
}

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

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

ll fac[2][maxn];
inline ll mul(ll n,int p,ll pk)
{
    if (!n) return 1;
    ll res=fac[p!=2][pk];
    res=Quick_power(res,n/pk,pk)%pk*fac[p!=2][n%pk]%pk;
    return res*mul(n/p,p,pk)%pk;
}

ll k,mod,ans,k2,k5;
inline ll C(ll n,ll m,int p,ll pk,bool div)
{
    if (n<0 || m<0 || n<m) return 0;
    ll zero=0;
    for (ll i=n; i; i/=p) zero+=i/p;
    for (ll i=m; i; i/=p) zero-=i/p;
    for (ll i=n-m; i; i/=p) zero-=i/p;
    if (p==2 && div) --zero;
    if (zero>=k) return 0;//优化
    ll a=mul(n,p,pk), b=mul(m,p,pk), c=mul(n-m,p,pk);
    ll res=Quick_power(p,zero,pk)*a%pk*inv(b,pk)%pk*inv(c,pk)%pk;
    if (p==5 && div) res=res*inv(2,pk)%pk;
    return res*(mod/pk)%mod*inv(mod/pk,pk)%mod;
}

inline ll exLucas(ll n,ll m,bool d)
{
    return (C(n,m,2,k2,d)+C(n,m,5,k5,d))%mod;
}

inline void pre(int p,ll pk)
{
    fac[p!=2][0]=1;
    for (int i=1; i<=pk; ++i)
        if (i%p) fac[p!=2][i]=fac[p!=2][i-1]*i%pk;
        else fac[p!=2][i]=fac[p!=2][i-1];
}

int main()
{
    pre(2,512), pre(5,1953125);
    ll a,b;
    while (~scanf("%lld %lld %lld",&a,&b,&k))
    {
        mod=Quick_power(10,k,1e9+5), k2=Quick_power(2,k,1e9+5), k5=Quick_power(5,k,1e9+5);
        if (a==b) ans=(Quick_power(2,a+b-1,mod)-exLucas(a<<1,a,1)+mod)%mod;
        else
        {
            ans=Quick_power(2,a+b-1,mod);
            for (ll i=(a+b)/2+1; i<a; ++i) (ans+=exLucas(a+b,i,0))%=mod;
            if ((a+b)%2==0) (ans+=exLucas(a+b,(a+b)/2,1))%=mod;
        }
        while (ans<mod/10) *IO::fe++=48, mod/=10;
        write(ans,'\n');
    }
    IO::flush();
    return 0;
}

template

其实我想借这篇 \(blog\) 来整理一下 \(exLucas\)

大意就是让这个东西: \(C_n^m\mod p\) ,其中 \(p\) 不为质数。

\(Lucas\) 定理都十分熟知了,和上面一样的式子,不过 \(p\) 为质数,所以可以这样快速求组合数:
\[ Lucas^n_m\equiv Lucas^{n/p}_{m/p}*C^{m\mod p}_{n\mod p}(\mod p) \]

然而现在好像不行了...

并且剧透一下, \(exLucas\)\(Lucas\) 除了名字挺像,要用的东西一点儿关系都没有。

这东西的前置技能是: \(crt\)\(exgcd\) (再次默认都会了)。

开始讨论过程:

我们令 \(P=\prod p_i^{c_i}\) ,那么如果我们知道每个 \(C_n^m\mod p_i^{c_i}\) ,就可以用 \(crt\) 直接合并求解,那怎么来求出这个值呢?

先把 \(C_n^m\) 写成 \(\frac{n!}{m!(n-m)!}\) ,现在我们可以处理阶乘的模。那么如何处理阶乘的模呢?

举个经典例子: \(p=3,n=19,c=2\) 时,我们可以吧式子写成这样:
\[ (19∗18∗17∗16∗15∗14∗13∗12∗11∗10∗9∗8∗7∗6∗5∗4∗3∗2∗1)\\ =(19∗17∗16∗14∗13∗11∗10∗8∗7∗5∗4∗2∗1)∗3^6∗6! \]

我们可以将他分为几个部分 \(19∗(17∗16∗14∗13∗11∗10)∗(8∗7∗5∗4∗2∗1)∗3^6∗6!\)

我们会发现对于每一个整的部分如 \((8∗7∗5∗4∗2∗1)\) 的模数都是一样的,于是这一块我们可以运用快速幂,而剩余的 \(19\) 我们可以进行暴力。

对于 \(6!\) 我们可以继续递归求解,那么怎么分组呢?把每一段的范围定为 \(p^c\) 即可。

\(code\) 放上:

#include<bits/stdc++.h>

typedef long long ll;

namespace IO
{
    char buf[1<<15],*fs,*ft;
    inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
    template<typename T>inline void read(T &x)
    {
        x=0;
        T f=1, ch=getchar();
        while (!isdigit(ch) && ch^'-') ch=getchar();
        if (ch=='-') f=-1, ch=getchar();
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }

    char Out[1<<24],*fe=Out;
    inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
    template<typename T>inline void write(T x,char str)
    {
        if (!x) *fe++=48;
        if (x<0) *fe++='-', x=-x;
        T num=0, ch[20];
        while (x) ch[++num]=x%10+48, x/=10;
        while (num) *fe++=ch[num--];
        *fe++=str;
    }
}

using IO::read;
using IO::write;

inline ll Quick_power(ll a,ll b,ll mod)
{
    ll ans=1;
    while (b)
    {
        if (b&1) ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans;
}

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

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

inline ll mul(ll n,ll p,ll pk)
{
    if (!n) return 1;
    ll ans=1;
    for (ll i=1; i<=pk; ++i)
        if (i%p) (ans*=i)%=pk;
    ans=Quick_power(ans,n/pk,pk);
    for (ll i=1; i<=n%pk; ++i)
        if (i%p) (ans*=i)%=pk;
    return ans*mul(n/p,p,pk)%pk;
}

inline ll C(ll n,ll m,ll p,ll pk)
{
    ll zero=0;
    for (ll i=n; i; i/=p) zero+=i/p;
    for (ll i=m; i; i/=p) zero-=i/p;
    for (ll i=n-m; i; i/=p) zero-=i/p;
    ll a=mul(n,p,pk), b=mul(m,p,pk), c=mul(n-m,p,pk);
    return Quick_power(p,zero,pk)*a%pk*inv(b,pk)%pk*inv(c,pk)%pk;
}

inline ll crt(ll n,ll p,ll pk)
{
    return inv(p/pk,pk)*(p/pk)*n;
}

inline ll exLucas(ll n,ll m,ll p)
{
    ll tmp=p, ans=0;
    for (ll i=2; i*i<=p; ++i)
    {
        ll k=1;
        while (tmp%i==0) k*=i, tmp/=i;
        (ans+=crt(C(n,m,i,k),p,k))%=p;
    }
    if (tmp>1) (ans+=crt(C(n,m,tmp,tmp),p,tmp))%=p;
    return ans%p;
}

int main()
{
    ll n,m,p;
    read(n);read(m);read(p);
    write(exLucas(n,m,p),'\n');
    IO::flush();
    return 0;
}

转载于:https://www.cnblogs.com/G-hsm/p/11452692.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值