LCM性質 + 組合數 - HDU 5407 CRB and Candies


題目描述

給定一個數n,求LCM(C(n,0),C(n,1),C(n,2)...C(n,n))的值,(n<=1e6)。題目鏈接

解題思路

很有趣的一道數論題!

看了下網上別人的做法,什么Kummer定理我還真沒聽說過,仔細研究一下那個鬼定理真是漲姿勢了!

然而這題我並不是用Kummer那貨搞的(what?).

其實這題真的很簡單(不要打我),為什么這樣說呢?看了下面的解釋你就知道我沒騙你。

首先我們看一下這個式子:LCM(C(n,0),C(n,1),C(n,2)...C(n,n))

當時我的第一感覺是:暈,還是打個表吧!結果,打表程序后台打了四個半小時也沒打完=.=(時間復雜度算錯了)

做這題首先你得知道這個(基本常識):

求多個數的最小公倍數,有兩種方法:

1)分解質因數法

先把這幾個數分解質因數,再把它們一切公有的質因數和其中幾個數公有的質因數以及每個數的獨有的質因數全部連乘起來,所得的積就是它們的最小公倍數。

例如,求LCM[12,18,20,60]

因為12=(2)×[2]×[3],18=(2)×[3]×3,20=(2)×[2]×{5},60=(2)×[2]×[3]×{5}

其中四個數的公有的質因數為2(小括號中的數),

三個數的公有的質因數為2與3[中括號中的數],

兩個數的公有的質因數為5{大括號中的數},

每個數獨有的質因數為3。

所以,[12,18,20,60]=2×2×3×3×5=180。

2)公式法

由於兩個數的乘積等於這兩個數的最大公約數與最小公倍數的積

即(a,b)×[a,b]=a×b。

所以,求兩個數的最小公倍數,就可以先求出它們的最大公約數,然后用上述公式求出它們的最小公倍數

例如,求[18,20]

即得[18,20]=18×20÷(18,20)=18×20÷2=180。

求幾個自然數的最小公倍數,可以先求出其中兩個數的最小公倍數,

再求這個最小公倍數與第三個數的最小公倍數,依次求下去,直到最后一個為止。

最后所得的那個最小公倍數,就是所求的幾個數的最小公倍數。

知道這個后,做這題選擇哪種方法呢?

如果選擇第二種方法,恭喜你,你絕壁和我一樣想到打表滾粗!

既然第二種方法不行,肯定只能是第一種方法了。

那么要怎么做呢?

首先我們來看,對於組合數C(n,m),可以有如下變換:

C(n,m)=n!/[(n-m)!*m!]=n*(n-1)*(n-2)*....(m+1) / (n-m)! 

這一步應該沒問題吧!

也就是:C(n,m)=n!/[(n-m)!*m!]=n*(n-1)*(n-2)*....(m+1) / (n-m)!  = n*(n-1)*(n-2)*....(m+1)/1/2/3/4/5/..../(n-m)

我們把前后結合一下,邊乘邊除:

對於第k步,就相當於*(n+1-k)且/k,k={1,2,...n-m}.

我們以n=8為例:

C(8,0)=1

C(8,1)=8*7*6*5*4*3*2 /7/6/5/4/3/2/1

C(8,2)=8*7*6*5*4*3 /6/5/4/3/2/1

C(8,3)=8*7*6*5*4 /5/4/3/2/1

C(8,4)=8*7*6*5 /4/3/2/1

C(8,5)=8*7*6 /3/2/1

C(8,6)=8*7 /2/1

C(8,7)=8 /1

C(8,8)=1

結合求n個數的LCM的方法,我們將問題轉換成:

找i個數共有的質數,然后相乘就可,i={1,2,..n}。

好了,你可能會說:*$#@*@,找i個數共有的質數難道不超時,而且你的代碼里連一個0~n的for循環都沒有,你在逗我?

不急,看下面:

首先我們明確一點,C(n,k)的最大質因數是不會大於n的。

那么對於一個質數p來說,他對"n個數的LCM"的貢獻在哪?

是不是就是p^1,p^2,p^3...中的一些?

哪些呢?

前面求組合數中,我們把C(n,m)分成了分子和分母來看。

如果p^x能夠整除(n-1+k),那么他有可能是滿足的,但是還不夠,還要看是不是會被分母抵消掉。

只有p^x滿足(n-1+k)%(p^x)==0且滿足k%(p^x)!=0,這個p^x才是滿足的,也就是對答案才有貢獻,此時ans需要乘以p。

最后一步,約約分可能會更方便:把分子分母合一下,變成了:(n-1)%(p^x)!=0,表示(n-1+k)%(p^x)==0和k%(p^x)!=0不是同時出現的,此時才滿足。

OK,推導完畢。

最終方法就是:

先篩出1e6以內的所有素數p,然后判斷(n-1)%(p^x)是否!=0,是的話,ans*=p。

 

時間復雜度

O(p_num*sqrt(n))

代碼

/*
* this code is made by crazyacking
* Verdict: Accepted
* Submission Date: 2015-08-21-15.17
* Time: 0MS
* Memory: 137KB
*/
#include <queue>
#include <cstdio>
#include <set>
#include <string>
#include <stack>
#include <cmath>
#include <climits>
#include <map>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
#define mod 1000000007
#define  LL long long
#define  ULL unsigned long long
using namespace std;
const int NN=1000010;
bool v[NN];
int p[NN],num;
void makePrime(){
   int i,j;
   num=-1;
   for(i=2; i<NN; ++i){
         if(!v[i]) p[++num]=i;
         for(j=0; j<=num && i*p[j]<NN; ++j){
               v[i*p[j]]=true;
               if(i%p[j]==0) break;
         }
   }
}
int main(){
   ios_base::sync_with_stdio(false);
   cin.tie(0);
   makePrime();
   int t;
   scanf("%d",&t);
   while(t--){
         int n;
         scanf("%d",&n);
         LL ans=1;
         for(int i=0; i<=num; ++i){
               for(LL t=p[i]; t<=n; t*=p[i]){
                     if((n+1)%t!=0)
                           ans=ans*p[i]%mod;
               }
         }
         printf("%lld\n",ans);
   }
   return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM