[ACM] HDU 3398 String (從座標0,0走到m,n且不能與y=x-1相交的方法數,整數唯一分解定理)
String
Problem Description Recently, lxhgww received a task : to generate strings contain '0's and '1's only, in which '0' appears exactly m times, '1' appears exactly n times. Also, any prefix string of it must satisfy the situation that the number of 1's can not be smaller than the number of 0's . But he can't calculate the number of satisfied strings. Can you help him?
Input T(T<=100) in the first line is the case number.
Each case contains two numbers n and m( 1 <= m <= n <= 1000000 ).
Output Output the number of satisfied strings % 20100501.
Sample Input 1 2 2
Sample Output 2
Author lxhgww
Source 解題思路:
題意為一個字串只由0,1組成,且0有m個,1有n個,要求該字串中任意的字首中1的個數不能小於0的個數,問這樣的字串一共有多少個。結果對20100501取模。
將構造字串的過程轉化到二維座標上去,1用y表示,0用x表示,從座標(0,0)出發,0代表向右走(x增加),1代表向上走(y增加),因為0有m個,1有n個,所以最後到達的座標為
(m,n) ,單純考慮從0,0走到m,n一共有C(n+m,m)種方法,又因為任意字首中1的個數不能小於0,所以y>=x,也就是合法走的路線經過的座標要麼在y=x上,要麼在其上邊,那麼
不合法的路線經過的座標則滿足y<x,也就是路線不能與y=x-1相交,因為只要一相交,就不滿足1的個數不能小於0的個數。
所以不合法的路徑一定與y=x-1相交,找到(0,0)關於y=x-1的對稱點(1,-1),從(1,-1)走到(m,n)一定與直線y=x-1相交,因為m,n在該直線的上邊,1,-1在該直線的下
邊。在這裡設交點為P,那麼路線就以P為分割點可以分為兩部分,上面一部分取個名字叫不合法路線,下面一部分取個名字叫合法路線。那麼從1,-1走到m,n有多少種方法也就
是從0,0走到m,n的不合法的方法數。仔細想一想為什麼呢?為什麼要找對稱點,在對稱直線一側走的路線總可以在另一側找到路線與之對稱,剛才我們從1,-1走到m,n的那一段
合法路線沿著y=x-1再對稱過去,不合法路線就不用對稱了,因為不合法路線已經與y=x-1相交了(其實那一段合法路線的最後一個點也與y=x-1相交),這樣就有了從0,0到m,n
的一種方案,總的來說就是對於從1,-1到m,n的每一條路線,都有從0,0到m,n關於y=x-1對稱的一條路線與之對應。從1,-1走到m,n方法數為C (m+n,m-1)
所以本題的答案就是C(n+m,m) - C (m+n,m-1)。
那麼接下來的問題就是求上面的式子了。
上面式子最終可以化為 (n+1-m)*(n+m)! / (m! *(n+1)!)
直接求肯定不行,因為涉及到了除法的取模運算。
考慮整數唯一分解定理:
任意正整數都有且只有一種方式寫出其素因子的乘積表示式。
A=(p1^k1)*(p2^k2)*(p3^k3)*....*(pn^kn) 其中pi均為素數
那麼可以把每個數都化成這樣的形式,然後上下對於相同的素因子進行約分,也就是指數相減,對於一個素數pi,我們只要知道最終pi的指數是多少就可以了(上下都約分後的指數)
還有把階層看作一個數,比m! 怎樣求m!裡面素數2的指數呢?
cnt=0; while(m) { m/=2; cnt+=m; } 就可以了,為什麼呢?考慮m=4,則m!= 4*3*2*1, 第一次m/=2,是計算m!裡面有多少個數能整除2的(有4,2),所以cnt+=2,有兩個數貢獻了兩個素數2,接下來第二次m/=2,是計算m!裡面有多少個數能整除4的,有1個數又貢獻了一個素數2.
程式碼:
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=1000000;
const int mod=20100501;
bool isprime[maxn*2+10];
int prime[maxn*2+10];
int len=0;//素數的個數
int n,m;
int t;
void sieve(int n)//篩n以內的素數
{
for(int i=0;i<=n;i++)
isprime[i]=1;
isprime[0]=isprime[1]=0;
for(int i=2;i<=n;i++)
if(isprime[i])
{
prime[len++]=i;
for(int j=2*i;j<=n;j+=i)
isprime[j]=0;
}
}
int cal(int p,int n)//計算n!裡面有多少個p相乘
{
int ans=0;
while(n)
{
n/=p;
ans+=n;
}
return ans;
}
int main()
{
sieve(maxn*2);
cin>>t;
while(t--)
{
long long ans=1;//記得要用long long
cin>>n>>m;
int nm=n+1-m;
for(int i=0;i<len&&prime[i]<=(n+m);i++)//prime[i]<=(n+m)是因為拆成素數冪相乘的形式該素數不會大於n+m,最大(n+m)! (n+m)*(n+m-1)*(n+m-2).....
{
int cnt=0;//分解為素數prime[i]的指數是多少
while(nm%prime[i]==0)//nm中有多少個prime[i],也就是把nm分解後prime[i]的指數
{
nm/=prime[i];
cnt++;
}
cnt=cnt+cal(prime[i],n+m)-cal(prime[i],m)-cal(prime[i],n+1);//加上分子的指數再減去分母的指數
for(int j=1;j<=cnt;j++)
{
ans=ans*prime[i];
if(ans>=mod)
ans%=mod;
}
}
cout<<ans<<endl;
}
return 0;
}