算法:大数运算,动态规划

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/aircattle/article/details/52559753

一个算法的老题目:
m*n的网格,一个机器人从左上角开始,走到右下角。
请问,有多少条路径?
注意:机器人每次只能向下或者向右移动一格,最终需要到达终点。
m和n均不超过100
格式:
第一行输入数字m和n,中间以空格间隔开。
第二行输出独特路径的数量。


样例输入
3 7
样例输出
28

分析:

       用动态规划的思想来想的话也不难想,a行b列的路径数量定义为f(a,b),m行,先对第一行进行何时向下走进行选择,有m个位置可以选,如果在第1个位置就向下,那么接下来有f(m-1,n)个路径走法,如果如果在第2个位置就向下,那么接下来有f(m-1,n-2)个路径走法,如果如果在第3个位置就向下,那么接下来有f(m-1,n-3)个路径走法,……如果如果在第m个位置就向下,那么接下来有f(m-1,n-m)个路径走法(假设m>m),这样就可以得出求f(m,n)的算法。

         另外,最快的方法是数学,根据排列组合,知道机器人总共向右移动n-1步,向下移动m-1步,那么就可以知道路径数量相当于从m+n-2个位置中选择n-1个出来,就是四则运算。

      然而,这个题目让人崩溃的是涉及到大数运算,因为中间计算时用到的数字可能远大于unsigned long long最大能表示的数。动态规划只涉及到加法,但数学算法涉及到乘法和除法。

      一开始我用动态规划算法来做,设计了大数加法的算法,预料之内要超时,m=100,n=100大概要100s,题目要求1s内。只好改成数学算法,设计乘法和除法,很快,肯定不会超时,但不知为什么在线提交时提示有测试点没过。不服啊,用matlab测试了很多数据,结果是没错的,而且把m<=100,n<=100内的所有结果输出,动态规划算法和数学算法的结果也是完全一样的,完全不知所以了。看了该题目的统计,提交了几千次,通过率不到2%,看来郁闷的不只我一个。不折腾了,大数运算算法还是值得一想。

大数均用string存储,因此会涉及到int数据到string的转换,借鉴这篇博客整形数据转换为字符串的研究,代码设计如下,一个是从个位数到char的转换,另一个是从unsigned long long到string的转换:

std::string & trim(std::string & str)
{
	if(str.empty())
	{
		return str;
	}
	str.erase(0,str.find_first_not_of(" "));
	str.erase(str.find_last_not_of(" ")+1);
	return str;
}
char convertIntToChar(const int & i)
{
	char cArr[10]={'0','1','2','3','4','5','6','7','8','9'};
	if(i<0|i>=10)
	{
		std::cout<<"The reference is unleagle."<<std::endl;
		return '0';
	}
	else
	{
		return cArr[i];
	}
		
}
std::string convertIntToString(const unsigned long long & num)
{
	char * cArr = "00010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899";
	unsigned long long i=num;
	if(i<0)
	{
		std::cout<<"convertIntToString can't have a negative reference."<<std::endl;
	}
	std::string str="";
	while(i>=100)
	{
		int iChar=i%100;
		int pos=iChar*2;
		str=cArr[pos+1]+str;
		str=cArr[pos]+str;
		i=i/100;
	}
	if(i>=10)
	{
		str=cArr[i*2+1]+str;
		str=cArr[i*2]+str;
	}
	else
		str=cArr[i*2+1]+str;
	return str;
}

大数加法,大数均用string存储:

std::string addString(std::string & str1,std::string & str2)
{
	str1=trim(str1);
	str2=trim(str2);
	int length1=str1.length();
	int length2=str2.length();
	int min=length1<length2?length1:length2;
	std::string strSum1,strSum2,strSum;
	
	int iCarry(0);
	for(int i=0;i<min;i++)
	{
		char c1=str1[length1-1];
		char c2=str2[length2-1];
		int iSum=atoi(&c1)+atoi(&c2)+iCarry;
		iCarry=iSum>=10?1:0;
		iSum%=10;
		char cSum=convertIntToChar(iSum);
		length1--;
		length2--;
		strSum1=cSum+strSum1;
	}
	
	if((length1|length2)!=0)
		if(length1==0)
		{
			std::string substr1=str2.substr(0,str2.length()-min);
			if(iCarry)
			{
				int num=substr1.length();
				while(num)
				{
					char cSub=substr1[num-1];
					int iSub=atoi(&cSub)+iCarry;
					iCarry=iSub>=10?1:0;
					iSub%=10;
					char cSum=convertIntToChar(iSub);
					strSum2=cSum+strSum2;
					num--;				
				}
				if(iCarry!=0)	
					strSum2='1'+strSum2;
			}
			else
				strSum2=substr1;
		}
		else	
		{
			std::string substr=str1.substr(0,str1.length()-min);
			if(iCarry)
			{
				int num=substr.length();
				while(num)
				{
					char cSub=substr[num-1];
					int iSub=atoi(&cSub)+iCarry;
					iCarry=iSub>=10?1:0;
					iSub%=10;
					char cSum=convertIntToChar(iSub);
					strSum2=cSum+strSum2;
					num--;				
				}
				if(iCarry!=0)	
					strSum2='1'+strSum2;
			}
			else
				strSum2=substr;
		}
	else
		strSum2=iCarry==1?"1":"";

	strSum=strSum2+strSum1;
	return strSum;
}

大数乘法,一样用string存储,其实用笔和纸在纸上把小学学的乘法运算列一下就大概知道怎么设计这个算法了,没什么难的。

std::string multiplyString(std::string & str1,std::string & str2)
{
	str1=trim(str1);
	str2=trim(str2);
	int length1=str1.length();
	int length2=str2.length();
	std::string strSum="";
	int a[400],b[400],c[400];
	char pChar;
	for(int i=0;i<length1;i++)
	{
		pChar=str1[i];
		a[i]=atoi(&pChar);
	}
	for(int j=0;j<length2;j++)
	{
		pChar=str2[j];
		b[j]=atoi(&pChar);
	}
	for(int i=length1+length2-2;i>=0;i--)
		c[i]=0;
	for(int i=0;i<length1;i++)
		for(int j=0;j<length2;j++)
			c[i+j]+=a[i]*b[j];
	int iCarry(0);
	for(int i=length1+length2-2;i>=0;i--)
	{
		c[i]+=iCarry;
		iCarry=c[i]/10;
		c[i]=c[i]%10;
	}
	if(iCarry)
		strSum+=char(48+iCarry);
	for(int i=0;i<=length1+length2-2;i++)
		strSum+=char(48+c[i]);
	return strSum;
}

大数除法,注意这里是一个string大数除以一个int类型数据,因为本程序中这么设计是能满足需求的:

std::string divideString(std::string & str1,const int iDivide)
{
	str1=trim(str1);
	int length1=str1.length();
	
	int a[400],b[400];
	char pChar;
	for(int i=0;i<length1;i++)
	{
		pChar=str1[i];
		a[i]=atoi(&pChar);
	}
	int iCarry(0);
	for(int i=0;i<length1;i++)
	{
		b[i]=(iCarry*10+a[i])/iDivide;
		iCarry=(iCarry*10+a[i])%iDivide;
	}
	int pos(0);
	while(b[pos]==0&&pos<length1)
		pos++;
	std::string strDivide="";
	for(int i=pos;i<length1;i++)
		strDivide+=char(48+b[i]);
	return strDivide;
}

动态规划主程序:

#include <iostream>
#include <stdlib.h>
#include <string>

int main()
{
	using namespace std;
	string PathQuantity[101][101];
	int m,n;
	cin>>m>>n;

	//DWORD start, stop;
	//start = GetTickCount();
	unsigned long long iPathQuantity[101][101];	
	int max=m>n?m:n;
	for(int i=1;i<=max;i++)
	{
		iPathQuantity[i][1]=1;
		iPathQuantity[1][i]=1;
		iPathQuantity[i][2]=i;
		iPathQuantity[2][i]=i;
		iPathQuantity[i][3]=i*(i+1)/2;
		iPathQuantity[3][i]=iPathQuantity[i][3];
	}
	int min=m<n?m:n;
	if(min<=3)
	{
		cout<<iPathQuantity[m][n];
		return 0;
	}

	for(int i=4;i<=34;i++)
		for(int j=4;j<=34;j++)
		{
			if(i>j&&i<=n)
			{
				iPathQuantity[i][j]=iPathQuantity[j][i];
				continue;
			}
			iPathQuantity[i][j]=0;
			for(int k=0;k<=j-1;k++)
				iPathQuantity[i][j]+=iPathQuantity[i-1][j-k];
		}
	if(m<=34&&n<=34)
	{
		cout<<iPathQuantity[m][n]<<endl;
		return 0;
	}

	for(int i=1;i<=max;i++)
	{
		PathQuantity[i][1]="1";
		PathQuantity[1][i]="1";
		PathQuantity[i][2]=convertIntToString(i);
		PathQuantity[2][i]=PathQuantity[i][2];
		PathQuantity[i][3]=convertIntToString(iPathQuantity[i][3]);
		PathQuantity[3][i]=PathQuantity[i][3];
	}
	for(int i=4;i<=m;i++)
		for(int j=4;j<=n;j++)
		{			
			if(i>j&&i<=n)
			{
				PathQuantity[i][j]=PathQuantity[j][i];
				continue;
			}
			if(i<=34&&j<=34)
			{
				PathQuantity[i][j]=convertIntToString(iPathQuantity[i][j]);
				continue;
			}
			PathQuantity[i][j]="0";
			for(int k=0;k<=j-1;k++)
				PathQuantity[i][j]=addString(PathQuantity[i-1][j-k],PathQuantity[i][j]);
		}
	cout<<PathQuantity[m][n]<<endl;
	return 0;
}

排列组合算法主程序:

#include <iostream>
#include <stdlib.h>
#include <string>

int main()
{
	using namespace std;
	int m,n;
	cin>>m>>n;
	int min=m>n?n:m;
	int max=m>n?m:n;
	if(min<=3)
	{
		if(min==1)
			cout<<"1"<<endl;
		if(min==2)
			cout<<max<<endl;
		if(min==3)
			cout<<(max*(max+1))/2<<endl;
		return 0;
	}
	string strNumerator("1");
	int num=min-1;
	for(int i=m+n-2;num>0;i--,num--)
	{
		string strMul=convertIntToString(i);
		strNumerator=multiplyString(strNumerator,strMul);
	}
	for(int i=1;i<=min-1;i++)
	{
		strNumerator=divideString(strNumerator,i);
	}
	cout<<strNumerator<<endl;
	return 0;

两个运算结果都是:

Matlab测试代码,注意结果是求得m=100,n=100时的C(198,99),结果与程序中两个算法结果都是一样的:

>> syms x;
>> limit('x+198!/99!/99!',x,0)
 
ans =
 
22750883079422934966181954039568885395604168260154104734000

 

展开阅读全文

没有更多推荐了,返回首页