1. 程式人生 > >[ACM] HDU 3398 String (從座標0,0走到m,n且不能與y=x-1相交的方法數,整數唯一分解定理)

[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;
}