1. 程式人生 > >【刷題】BZOJ 4830 [Hnoi2017]拋硬幣

【刷題】BZOJ 4830 [Hnoi2017]拋硬幣

ble 情況 相互 tput 不足 DC gcd rac 十進制

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

Sample Output

000000004
6
3 2 1
【樣例解釋】
對於第一組數據,當小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)

Solution

膜拜litble大佬
大佬思路很巧妙啊
首先把兩個人拋硬幣的結果接在一起作為一個01序列;那麽對於小A勝利的情況就是前 \(a\) 項包含的1大於後 \(b\) 項包含的1,對於小B勝利的情況正好相反,但是如果把小B勝利的序列所有位置都異或1,那麽新序列對應的一定是一個小A勝利的序列;所以小A和小B勝利的序列是可以相互轉化的,且一一對應,所以正常情況下,小A勝利和小B勝利各占一半,即 \(\frac{2^{a+b}}{2}\)

  • \(a=b\) 的時候,要減去平局的情況,答案是 \(\frac{2^{a+b}-C_{a+b}^a}{2}\) 。為什麽平局的情況是 \(C_{a+b}^a\)

    ?這個得換一種構建序列的思路,小A拋硬幣是正面為1,反面為0,而小B拋硬幣是正面為0,反面為1,;這樣構建,如果他們拋出硬幣是正面的次數相等,那麽序列裏1的數量一定等於0的數量,又 \(a=b\) ,所以就是 \(a+b\) 裏選 \(a\) 個當1,即 \(C_{a+b}^a\)

  • \(a>b\) 的時候,會出現這樣一種情況:01序列異或1之後前 \(a\) 項包含1的個數仍然大於後 \(b\) 項包含1的個數。這樣的情況我們要額外加上。對於這樣的情況,設原序列中小B \(i\) 次正面,而小A比小B多 \(j\) 次正面,因為異或1之後的序列仍然小A的1更多,即 \(b-i<a-i-j\) ,所以 \(j<a-b\)
    那麽 \(extra=\frac{\sum_{i=0}^b\sum_{j=1}^{a-b-1}C_b^iC_a^{i+j}}{2}=\frac{\sum_{i=0}^b\sum_{j=1}^{a-b-1}C_b^{b-i}C_a^{i+j}}{2}\)
    然後怎麽辦?
    litble大佬給出了一種理解方法

    那麽我們可以這麽看這個式子:我們在 \(a+b\) 個數組成的序列中選擇 \(b+j\) 個元素變成1,然後其中在前 \(b\) 個元素中的1的個數就正好能完成那個和 \(i\) 有關的枚舉。

    所以額外的貢獻又變成了:\(extra=\frac{\sum_{i=1}^{a-b-1}C_{a+b}^{b+i}}{2}\)
    好好變式子:\(extra=\frac{\sum_{i=b+1}^{a-1}C_{a+b}^i}{2}=\sum_{i=\lfloor \frac{a+b}{2} \rfloor}^{a-1}C_{a+b}^i+[2|a+b]\frac{C_{a+b}^{\frac{a+b}{2}}}{2}\)
    所以 \(ans=2^{a+b-1}+extra\)
    就差不多了,然後暴力枚舉,用拓展Lucas求組合數

幾個問題:

  • 怎麽用擴展Lucas?
    發現模數是10的多少次方,這一定可以變成一個2的多少次方乘5的多少次方;這兩個東西正好滿足擴展Lucas的形式,所以可以求

  • 要預處理嗎?
    要。因為模數只會是10的多少次方,那就把2和5在擴展Lucas求fac之中的for循環枚舉的乘積先預處理存下來,大大節省時間,不加這個不行

  • 為什麽 \(extra\) 一定要化到最後一步呢?
    兩個原因。一是可以節省時間;二是保證正確性。我們在求Lucas中對於除2的處理很特殊,在模數是5的多少次方時,因為2和5的多少次方互質,所以可以直接擴歐求逆元乘上去,但如果是2的多少次方當模數的時候,2是沒有逆元的,怎麽辦?那就只能在擴展Lucas裏提因子 \(pi=2\) 時,把最後要乘的2的次方減去一個1。但是這樣肯定會有問題,因為組合數是有奇數的,這時沒有2因子,毫無辦法,沒救了。而最後一步式子利用楊輝三角的對稱性把左右兩邊的一起算,算2倍的,除2之後就是本身的值,唯一多出來的就是後面的 \([2|a+b]\frac{C_{a+b}^{\frac{a+b}{2}}}{2}\) ,但這個式子要不前面條件不成立,要麽一定有2因子(畫一畫楊輝三角),所以可以保證答案正確性

#include<bits/stdc++.h>
#define ui unsigned int
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
const int MAXN=100+10,MAX2=512,MAX5=1953125;
ll a,b,k,Mod,p1,p2,pk1,pk2,f[2][MAX5+10];
template<typename T> inline void read(T &x)
{
    T data=0,w=1;
    char ch=0;
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')w=-1,ch=getchar();
    while(ch>='0'&&ch<='9')data=((T)data<<3)+((T)data<<1)+(ch^'0'),ch=getchar();
    x=data*w;
}
template<typename T> inline void write(T x,char ch='\0')
{
    if(x<0)putchar('-'),x=-x;
    if(x>9)write(x/10);
    putchar(x%10+'0');
    if(ch!='\0')putchar(ch);
}
template<typename T> inline void chkmin(T &x,T y){x=(y<x?y:x);}
template<typename T> inline void chkmax(T &x,T y){x=(y>x?y:x);}
template<typename T> inline T min(T x,T y){return x<y?x:y;}
template<typename T> inline T max(T x,T y){return x>y?x:y;}
inline ll qexp(ll a,ll b,ll n)
{
    ll res=1;
    while(b)
    {
        if(b&1)res=res*a%n;
        a=a*a%n;
        b>>=1;
    }
    return res;
}
inline ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(b==0)
    {
        x=1;
        y=0;
        return a;
    }
    ll r=exgcd(b,a%b,x,y);
    ll t=x;
    x=y;
    y=t-(a/b)*y;
    return r;
}
inline ll fac(ll n,ll pi,ll pk)
{
    if(!n)return 1;
    else return qexp(f[pi!=2][pk],n/pk,pk)*f[pi!=2][n%pk]%pk*fac(n/pi,pi,pk)%pk;
}
inline ll inv(ll n,ll p)
{
    ll x,y;
    exgcd(n,p,x,y);
    return (x+p)%p==0?p:(x+p)%p;
}
inline ll C(ll n,ll m,ll pi,ll pk,bool nd)
{
    if(n<m)return 0;
    ll ki=0;
    for(register ll i=n;i;i/=pi)ki+=i/pi;
    for(register ll i=m;i;i/=pi)ki-=i/pi;
    for(register ll i=n-m;i;i/=pi)ki-=i/pi;
    if(pi==2&&nd)ki--;
    if(ki>=k)return 0;
    ll Mul1=fac(n,pi,pk),Mul2=fac(m,pi,pk),Mul3=fac(n-m,pi,pk),inv2=inv(2,pk);
    return Mul1*inv(Mul2,pk)%pk*inv(Mul3,pk)%pk*qexp(pi,ki,pk)%pk*(pi==5&&nd?inv2:1)%pk;
}
inline ll CRT(ll B,ll W)
{
    return B*(Mod/W)%Mod*inv(Mod/W,W)%Mod;
}
inline ll exLucas(ll n,ll m,bool nd)
{
    p1=2,p2=5;
    pk1=1,pk2=1;
    for(register int i=1;i<=k;++i)pk1*=p1,pk2*=p2;
    return (CRT(C(n,m,p1,pk1,nd),pk1)+CRT(C(n,m,p2,pk2,nd),pk2))%Mod;
}
inline void init()
{
    f[0][0]=f[1][0]=1;
    for(register int i=1;i<=MAX2;++i)
        if(i%2)f[0][i]=1ll*f[0][i-1]*i%MAX2;
        else f[0][i]=f[0][i-1];
    for(register int i=1;i<=MAX5;++i)
        if(i%5)f[1][i]=1ll*f[1][i-1]*i%MAX5;
        else f[1][i]=f[1][i-1];
}
int main()
{
    init();
    while(scanf("%lld%lld%d",&a,&b,&k)!=EOF)
    {
        Mod=1;
        for(register int i=1;i<=k;++i)Mod*=10;
        ll res=0;
        if(a==b)res=(qexp(2,a+b-1,Mod)-exLucas(a+b,a,1)+Mod)%Mod;
        else
        {
            res=qexp(2,a+b-1,Mod);
            for(register ll i=(a+b)/2+1;i<a;++i)(res+=exLucas(a+b,i,0))%=Mod;
            if((a+b)%2==0)(res+=exLucas(a+b,(a+b)/2,1))%=Mod;
        }
        while(res<(Mod/10))write(0),Mod/=10;
        write(res,'\n');
    }
    return 0;
}

【刷題】BZOJ 4830 [Hnoi2017]拋硬幣