HDU1033 Buy the Ticket(卡特兰 大数)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1133
怎么说呢,做这道题还是感觉很艰难的,最开始我一直没有想明白,到底是怎么排列的。虽然知道是用卡特兰数,但是感觉自己的推理能力还是有限,搞了一下午,终于弄好了。
大概的理解是这样的:跟数的进栈出栈是一样的原理,不过这里用位来分析,可能更直观。假设50的表示为0,100表示为1.一共有m个0和n个1.用m+n位就可以表示。m+n位中选n个来放0,则剩下的用来放1,这样排列出来,总数应该是C(m+n,n)。
下面来分析不满足情况的排列:
我们知道只要100的第一次出现在50之前,那么这种排列就是不满足要求的。也就是1出现在0之前,有如0110.......。这里不妨设在第三位的时候2k+1位上第一次出现1在0之前了,那么前面有k+1个1和k个0,后面会有n-k-1个1和m-k个0。将后面的0和1相互对调,这样排列中就有m+1个1和n-1个0。这样对调后的排列与不满足条件的排列是一一对应的,所以不满足条件的排列的总数的C(m+n,m+1)。
参考资料:http://baike.baidu.com/view/2499752.htm
当然所有的排列,要么满足条件,要么不满足条件。故满足条件的排列数为:C(m+n,n)-C(m+n,m+1);化简之后可得到(m+n)!*(m-n+1)/(m+1),注意到这里涉及到阶乘所以要考虑大数的相乘。而大数的乘法相对于除法来讲要简单得多。所以将公式化简一下。当n=0时,结果即(m+n)! ;当n>0时,在阶乘中跳过m+1。
好!下面就看一下代码。代码中用字符串表示大数,在字符串中从0~后面,是从低位到高位,最后加一个‘#’标记束。

#include<stdio.h>
#include <string.h>
#include<stdlib.h>
#define M 10000
char str[M];
//算100以内的阶乘,存放在fact里面
char fact[205][M];
//用字符串表示大数,以'#'号结尾
//str1和str2是乘数,str是结果
int Mutiply(char *str1, char *str2, char *str)
{
	int i,j;
	int a,b;
	int Result[M];
	memset(Result,0,sizeof(Result));

	for(i = 0; str1[i] != '#'; ++i)
	{
		a = (int)(str1[i] - '0');
		for(j = 0; str2[j] != '#'; ++j)
		{
			b = (int)(str2[j] - '0');
			Result[i + j] += a * b;
		}
	}
	j += i - 1;
	i = 0;
	//到了最高位,如果不为零,就一直赋值。
	for(i = 0; (i < j || Result[i] > 0); ++i)
	{
		str[i] = Result[i] % 10 + '0';
		Result[i+1] += Result[i] / 10;
	}

	str[i] = '#';//加结束标志
	return i;
}
//nLen表示所有字符的个数
void Invert(char *str, int nLen)
{
	int i;
	char temp;

	for(i = 0; i < (nLen >> 1); ++i)
	{
		temp = str[i];
		str[i] = str[nLen - i - 1];
		str[nLen - i - 1] = temp;
	}
}
void Print(char *str, int nLen)//打印
{
	int i;
	for(i = 0; i < nLen; ++i)
	{
		putchar(str[i]);
	}
	printf("\n");
}
//计算阶乘
int Fact(int a, int b)
{
	char buf[15];
	int nLen;
	fact[0][0] = '0';
	fact[1][0] = '1';
	fact[1][1]= '#';//记得加结束标志
	for(int i = 2; i <= a; ++i)\
	{
		if(i == b)
		{
			memcpy(fact[i], fact[i - 1], (nLen + 2) * sizeof(char));
			continue;
		}
		itoa(i,buf, 10);
		nLen = strlen(buf);
		buf[nLen] = '#';//记得加结束标志
		buf[nLen + 1] = 0;
		Invert(buf,nLen);
		nLen = Mutiply(fact[i - 1], buf, fact[i]);
		fact[i][nLen] = '#';//记得加结束标志
	}
	return nLen;
}
void main()
{
	int m,n;
	int nLen;
	int t = 1;
	char buf[5];
	while (scanf("%d %d", &m, &n))
	{
		if(!m && !n)
		{
			break;
		}
		if(m < n)
		{
			printf("Test #%d:\n0\n",t++);
			continue;
		}
		nLen = Fact(m+n,m+1);

		printf("Test #%d:\n",t++);
		if(n)//如果n不为零,则需要乘(m-n+1)
		{
			itoa(m - n + 1,buf,10);
			nLen = strlen(buf);
			buf[nLen] = '#';
			buf[nLen + 1] = 0;
			Invert(buf,nLen);
			nLen = Mutiply(fact[m+n],buf,str);

			Invert(str, nLen);
			Print(str, nLen);
		}
		else
		{
			Invert(fact[m],nLen);
			Print(fact[m], nLen);
		}		
	}
}

你可能感兴趣的:(HDU1033 Buy the Ticket(卡特兰 大数))