2013年第四届蓝桥杯C/C++程序设计本科B组省赛 带分数(全排列next_permutation)


标题:带分数

    100 可以表示为带分数的形式:100 = 3 + 69258 / 714

    还可以表示为:100 = 82 + 3546 / 197

    注意特征:带分数中,数字1~9分别出现且只出现一次(不包含0)。

    类似这样的带分数,100 有 11 种表示法。

题目要求:
从标准输入读入一个正整数N (N<1000*1000)100 0000
程序输出该数字用数码1~9不重复不遗漏地组成带分数表示的全部种数。
注意:不要求输出每个表示,只统计有多少表示法!


例如:
用户输入:
100
程序输出:
11

再例如:
用户输入:
105
程序输出:
6


资源约定:
峰值内存消耗 < 64M
CPU消耗  < 3000ms


请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。

所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。

注意: main函数需要返回0
注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。
注意: 所有依赖的函数必须明确地在源文件中 #include , 不能通过工程设置而省略常用头文件。

提交时,注意选择所期望的编译器类型。


  

 

解题思路:

该题的条件是 (x+(y/z)==n)&&(y%z==0)并且x,y,z是1~9九个数字组成的,每个数字只能用一次。

所以就可以将x,y,z三个数放在一起看成一个九个数的序列,对该序列进行全排列,每次全排列划分不同的数给x,y,z,如果符合条件则计数器+1。

这样写太耗时,可能后台评测数据太水,勉强没有超时。

 

AC代码:

#include
#include
using namespace std;

int main()
{
	int a[9]={1,2,3,4,5,6,7,8,9};
	int n,cnt=0;
	scanf("%d",&n);
	do
	{
		int x=0,y=0,z=0;
		for(int i=0;i<7;i++)
		{
			x=x*10+a[i];
			y=0;
			for(int j=i+1;j<8;j++)
			{
				y=y*10+a[j];
				z=0;
				for(int k=j+1;k<9;k++)
				{
					z=z*10+a[k];
				}
				//printf("x===%d y===%d z===%d\n",x,y,z);
				if((x+(y/z)==n)&&(y%z==0))
				{
					cnt++;
				}
			}
		}
	}while(next_permutation(a,a+9));
	printf("%d\n",cnt);
	return 0;
}

以下是关于这道题更完美的解法:

https://blog.csdn.net/jopus/article/details/18998403

 

其中关于全排列的一部分思想:

根据题目的要求,输入一个数字,需要将这个数字化成带分数,且包含(0-9)所有数字(不重复)。

这里我们假设输入的数字为number,划分好的带分数为a+b/c,它将满足条件number == a+b/c且b%c==0,同时abc包含所有(0-9,且不重复)。

请注意最后最句话,abc包含所有(0-9,且不重复),我们再看题目给我们的那个满足条件的数字:

3+69258/714   这里a = 3, b = 69258, c = 714 ;则abc = 369258714(这个数字数list的一种排列方式)。

所以我们想到一种方法:先求出list的一种排列,然后对这个排列进行a,b,c划分处理,如果满足条件

(条件:number == a+b/c且b%c==0,同时abc包含所有(0-9,且不重复))则将记录种数+1 。

 

上面已经分析了,将“123456789”全排列,然后用a,b,c划分,我们这里主要讲要怎么划分:

还是用上面那个排列:abc = 369258714

我们规定a在最前面,b在中间,c在尾部(因为对list进行了全排列每种情况都会出现,所以a,b,c位置无所谓,这么规定只是为了好分析)。

这样我们可以知道a的头部就是list的第一位list[0],a的尾部就是b的头部(不能确定)

b的尾部则是c的头部(也不能确定),但是我们知道c的尾部一定是list[8]。

然后又有number = a + b / c 等式成立。变形一下:b = (number - a) * c。

好了,注意,根据乘法原理我们可以推出b的尾部数字(设为bLast)。

bLast=((number-a)*list[8])%10;    //确定b最后一个数字。

最后为了程序优化,我们剪掉一些不可能的情况。

下面我们对a,b,c的区间进行分析:

number = a + b / c

a为带分数的左边,(1<= a <= 1000,000)这个范围没错吧?1000,000为题目规定范围。我们用for循环人为设定a区间的尾部。

b为带分数的上部,这里隐含条件(b % c == 0)必须为整数。所以b>=c这点是必须的,由于我们划分的是区间,

所以区间长度就能近似的表示数值的大小了,区间越长,说明数值位数越多,肯定值越大,所以b的区间长度就必须不小于c的区间长度。

也就是说a后面剩下的list长度b要占一半以上。因为b的最后一位我们已经算出来了,所以我们需要匹配最后一位的位置,就能划分出a,b,c区间。

说了这么一大堆,我自己都搞晕了。放个例子出来溜溜,还是以上面那个排列来说明。

 

示例:3+69258 / 714   (number = 100)

abc = 369258714

我们是这么来划分的:

第一步:人为划分a(如:(0,1)),即a = 3

第二步:计算出bLast=((number-a)*list[8])%10 = ((100 - 3) * 4)%10 = 8

第三步:a = (0,1)那么bc则是(2,8),那么b的最后一位我们从(8-2)/2 = 3,也就是从list[3] = 2开始找起(直接跳过前面不可能情况)  369-258714

第四步:判断bLast ==  list[i],当找到了b的尾部的时候,我们就开始检查组合的合理性:它将满足条件number == a+b/c且b%c==0(如果合理)

第五步:如果合理则将情况种数+1同时跳出for循环,进入递归找下一组排列。

如果不合理,则将a划分区间长度+1,a = (0,2),即a = 36,然后再确定b的尾部bLast.................

下面是一个简单的图分析:

 

通过上面分析我们可以优化全排列算法,更快的找到分子取值的位置节省时间,建议看原文,了解全排列的手动实现的过程。

https://blog.csdn.net/jopus/article/details/18998403

你可能感兴趣的:(ACM,蓝桥杯,笔记)