1. 程式人生 > >[ACM] POJ 1942 Paths on a Grid (組合)

[ACM] POJ 1942 Paths on a Grid (組合)

遞歸 clu 位數 pict 數位 end lesson weight 運算

Paths on a Grid
Time Limit: 1000MS Memory Limit: 30000K
Total Submissions: 21297 Accepted: 5212

Description

Imagine you are attending your math lesson at school. Once again, you are bored because your teacher tells things that you already mastered years ago (this time he‘s explaining that (a+b)2
=a2+2ab+b2). So you decide to waste your time with drawing modern art instead.

Fortunately you have a piece of squared paper and you choose a rectangle of size n*m on the paper. Let‘s call this rectangle together with the lines it contains a grid. Starting at the lower left corner of the grid, you move your pencil to the upper right corner, taking care that it stays on the lines and moves only to the right or up. The result is shown on the left:
技術分享

Really a masterpiece, isn‘t it? Repeating the procedure one more time, you arrive with the picture shown on the right. Now you wonder: how many different works of art can you produce?

Input

The input contains several testcases. Each is specified by two unsigned 32-bit integers n and m, denoting the size of the rectangle. As you can observe, the number of lines of the corresponding grid is one more in each dimension. Input is terminated by n=m=0.

Output

For each test case output on a line the number of different art works that can be generated using the procedure described above. That is, how many paths are there on a grid where each step of the path consists of moving one unit to the right or one unit up? You may safely assume that this number fits into a 32-bit unsigned integer.

Sample Input

5 4
1 1
0 0

Sample Output

126
2

Source

Ulm Local 2002
簡單題,求解 C(n+m, m) .

代碼:

#include <iostream>
#include <algorithm>
using namespace std;

long long c(long long n,long long m)
{
    long long ans=1;
    for(int i=1;i<=m;i++)
        ans=ans*(n--)/i;
    return ans;
}

int main()
{
    long long n,m;
    while(cin>>n>>m&&(n||m))
    {
        if(n<m)
            swap(n,m);
        cout<<c(n+m,m)<<endl;
    }
    return 0;
}

http://blog.csdn.net/lyy289065406/article/details/6648516在裏面學到了兩種新的求解組合數的方法。


處理階乘有三種辦法:

(1) 傳統意義上的直接遞歸。n的規模最多到20+,太小了,在本題不適用,並且很慢

(2) 稍快一點的算法,就是利用log()化乘為加。n的規模盡管擴展到1000+,可是因為要用三重循環,一旦n規模變得更大。耗時就會很之嚴重,時間復雜度達到O(n*m*(n-m)),本題規定了n,m用unsigned int32類型,就是說n,m的規模達到了21E以上。鐵定TLE的。

並且就算拋開時間不算,還存在一個致命的問題,就是精度損失隨著n的添加會變得很嚴重。

由於n有多大。就要進行n次對數運算。n規模一旦過大,就會丟失得很嚴重了。所以這樣的方法是絕對不可取的,由於中途的精度丟失不是簡單的四舍五入能夠挽回的。

(3) 拆分階乘。逐項相除,再乘曾經面全部項之積。

這樣的方法用一個循環就OK了。時間復雜度僅僅有O(n-m),很可觀。

以下我依據程序具體說說算法(3):

double cnm=1.0;

while(b>0)

cnm*=(double)(a- -)/(double)(b- -);

這是我寫的函數原型。計算的是 aCb

這樣的算法巧妙地利用了分子分母的關系,而不是把公示中的3個階乘單獨處理。

比如當 a=5,b=2時

技術分享

因為用了 double去計算組合數。那麽最後要轉化為 無符號整型 時就要處理精度問題,有兩種方法:四舍五入+強制類型轉換 或者 用 setprecision()函數


  1. unsigned comp(unsigned n,unsigned m)
  2. {
  3. unsigned a=m+n;
  4. unsigned b=(m<n?m:n);
  5. double cnm=1.0;
  6. while(b>0)
  7. cnm*=(double)(a--)/(double)(b--);
  8. cnm+=0.5; //double轉unsigned會強制截斷小數。必須先四舍五入
  9. return (unsigned)cnm;
  10. }

  1. double comp(unsigned n,unsigned m)
  2. {
  3. unsigned a=m+n;
  4. unsigned b=(m<n?m:n);
  5. double cnm=1.0;
  6. while(b>0)
  7. cnm*=(double)(a--)/(double)(b--);
  8. return cnm;
  9. }
  1. cout<<fixed<<setprecision(0)<<comp(n,m)<<endl;
  2. //fixed是為了固定小數位數
  3. //setprecision()函數是會自己主動四舍五入的,所以不用像強制類型轉換那樣預先+0.5

[ACM] POJ 1942 Paths on a Grid (組合)