1. 程式人生 > >BZOJ 2142 淺談LuCas EXtra拓展盧卡斯定理解合數組合式

BZOJ 2142 淺談LuCas EXtra拓展盧卡斯定理解合數組合式

這裡寫圖片描述
世界真的很大
今天考試的第一題,講道理拓展Lucas算NOIP?
還好學會了
考試的時候一看,思路和解法都非常簡單,簡單到已經不是這道題的主要部分了,求幾個組合數就完了,模擬了一下樣例過了,很開心
然後一看,嗯?mod怎麼是一個合數呢?
EXLucas

看題先:

description:

一年一度的聖誕節快要來到了。每年的聖誕節小E都會收到許多禮物,當然他也會送出許多禮物。不同的人物在小E心目中的重要性不同,在小E心中分量越重的人,收到的禮物會越多。小E從商店中購買了n件禮物,打算送給m個人,其中送給第i個人禮物數量為wi。請你幫忙計算出送禮物的方案數(兩個方案被認為是不同的,當且僅當存在某個人在這兩種方案中收到的禮物不同)。由於方案數可能會很大,你只需要輸出模P後的結果。

input:

輸入的第一行包含一個正整數P,表示模;第二行包含兩個整整數n和m,分別表示小E從商店購買的禮物數和接受禮物的人數;以下m行每行僅包含一個正整數wi,表示小E要送給第i個人的禮物數量。

output:

若不存在可行方案,則輸出“Impossible”,否則輸出一個整數,表示模P後的方案數

首先一句話談談題解,然後淺談一下拓展盧卡斯
考慮一共要用SUM個禮物,如果比n大的話肯定就是Impossible
不到n的話呢,先列舉從n個裡面選SUM個,然後從SUM裡面選w1個,從SUM-w1裡面選w2個,從SUM-w1-w2裡面選w3個,以此類推
寫出來就是C(n,SUM) * C(SUM,w1) * C(SUM-w1,w2) 。。。。
而且m只有5,感覺太少了一點,但是的確是這樣
問題就出在這個組合數是一個合數。
Lucas定理都是出在mod為質數的情況下。
拓展lucas應運而生

現在來談一談拓展Lucas
我們要求的是C(n,m) % mod,mod是一個合數
其實合數的問題主要出在哪裡呢?
即求的是 n! / m!/ (n-m)!
如果不是合數,我們採用的方法是處理出m!和(n-m)!的逆元
但是逆元必須要在與mod數互質的情況下才有

對於這一點,我們的處理方法是,把合數mod拆分成如下形式:
P1^a1 * P2^a2 * P3^a3 …………..
其中pi都是質數
考慮對於P1^a1 , P2^a2。。。。他們都是互質的。
考慮對於每一個Pi^ai作為mod數求出C(n,m)的值,就可以用中國剩餘定理即孫子定理求出來最後的值
那麼現在的問題就變成了求出每一個C(n,m) % Pi^ai的值
即n! / m!/ (n-m)! % Pi^ai
由於我們無法保證n!,m!,(n-m)!與Pi是互質的,所以暫時還是無法使用逆元
既然如此,我們由於Pi^ai的質因數只有有1個Pi,我們就考慮把這階乘中的Pi全部提出來,變成x * Pi^y的形式
然後x與Pi互質,就可以使用逆元處理,Pi^y在分子和分母上可以通過指數的加加減減得到
即把原式處理成 :
x1 * Pi^y1 / (x2 * Pi^y2 x3

Pi^y3)的形式
那麼現在問題就在於如何把n!處理成x * Pi^y的形式

舉個例子,20!在mod3^2的意義下處理成 x * 3 ^y
首先先把20!裡面含有3的提出來,如3,6,9,12,15,18,一共有6項
提出3^6,那麼還剩下6! * 1 * 2 * 4* 5 *7 *8 *10 *11 *13 *14 *16 *17 *19 *20
對於這些數,在mod 3^2的意義下,就存在有迴圈節,1-9,10-18
每一個迴圈節乘起來mod 9 的值是相同的,算出一個的值然後快速冪就可以得到
然後還剩下的19和20就暴力算出
6!就變成了20!的子問題,可以遞迴呼叫函式求解

中國剩餘定理就不講了,應該都會把,簡單版本的

完整程式碼:

#include<stdio.h>
typedef long long dnt;

dnt mod,n,w[200010],SUM=0;
int m;

dnt quickmub(dnt a,dnt b,dnt m)
{
    dnt rt=1;
    while(b)
    {
        if(b&1) rt=rt*a%mod;
        a=a*a%mod,b>>=1;
    }
    return rt;
}

dnt exgcd( dnt a, dnt b, dnt &x, dnt &y ) 
{
    if(b==0) 
    {
        x=1,y=0;
        return a;
    }   
    dnt x0,y0;
    dnt cd=exgcd(b,a%b,x0,y0);
    x=y0;
    y=x0-(a/b)*y0;
    return cd;
}

dnt inverse( dnt a ,dnt m) 
{
    dnt x, y;
    exgcd(a,m,x,y);
    return (x%m+m)%m;
}

dnt Divide(dnt x,dnt prm,dnt prk)
{
    if(x==0) return 1;
    dnt tmp=1;
    for(int i=1;i<=prk;i++)
        if(i%prm) tmp*=i,tmp%=prk;
    tmp=quickmub(tmp,x/prk,prk);
    dnt tmp2=x%prk;
    for(int i=2;i<=tmp2;i++)
        if(i%prm) tmp*=i,tmp%=prk;
    tmp=tmp*Divide(x/prm,prm,prk)%prk;
    return tmp%prk;
}

dnt Misaka(dnt a,dnt b,dnt prm,dnt prk)
{
    dnt tmp1=Divide(a,prm,prk);
    dnt tmp2=Divide(b,prm,prk);
    dnt tmp3=Divide(a-b,prm,prk);
    dnt tmp=a,tot=0;
    while(tmp) tot+=tmp/prm,tmp/=prm;
    tmp=b;
    while(tmp) tot-=tmp/prm,tmp/=prm;
    tmp=a-b;
    while(tmp) tot-=tmp/prm,tmp/=prm;
    dnt bns=tmp1*inverse(tmp2,prk) %prk*inverse(tmp3,prk)%prk;
    dnt cns=bns*quickmub(prm,tot,prk)%prk;
    return  cns*(mod/prk)%mod*inverse(mod/prk,prk)%mod;
}

dnt CRT(dnt a,dnt b)
{
    dnt tmp=mod,rt=0;
    for(int i=2;i<=tmp;i++)
    {
        dnt lt=1;
        while(tmp%i==0) tmp/=i,lt*=i;
        if(lt!=1) rt=(rt+Misaka(a,b,i,lt))%mod;
    }
    return rt%mod;
}

int main()
{
    scanf("%lld",&mod); 
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) 
    {
        scanf("%lld",&w[i]),SUM+=w[i];
        if(SUM>n)
        {
            printf("Impossible\n");
            return 0;
        }
    }
    dnt ans=CRT(n,SUM)%mod;
    for(int i=1;i<=m;i++)
        ans=ans*CRT(SUM,w[i])%mod,SUM-=w[i];
    printf("%lld\n",ans);
    return 0;
}
/*
EL PSY CONGROO
*/

嗯,就是這樣