poj An easy problem
给定一个矩形网格的长m和高n,其中m和n都是unsigned int32类型,一格代表一个单位,就是一步,求从左下角到右上角有多少种走法,每步只能向上或者向右走
解题思路:
非常水的中学数学题,用组合做
先简单建立一个数学模型:
只要给定了长m和高n,那么要从左下角走到右上角,不管怎么走,一定要往右走m次,往上走n次
例如给定 m=5,n=4
那么可以 上上上上上右右右右
又可以 上右上右上右上右上
等等。。。
关键是“上”和“右”的先后问题,就是组合问题了
那么数学模型就是
从n+m个位置,选择n个位放“上” (那么剩下m个位一定是“右”)
处理阶乘有三种办法:
(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),非常可观。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
int min(unsigned int n,unsigned int m)
{
if(n<m)
return n;
return m;
}
double fun(unsigned int n,unsigned int m)
{
double tns=1;
while(m>0)
{
tns*=(double)(n--)/(double)(m--);
}
return tns;
}
int main()
{
int n,m;
while(scanf("%d %d",&n,&m),n||m)
{
unsigned int sum=n+m;
n=min(n,m);
double ans=fun(sum,n);
printf("%.0lf\n",ans);
}
return 0;
}