蓝桥杯 带分数

来源:互联网 发布:网络信息安全问题介绍 编辑:程序博客网 时间:2024/05/04 23:24

这可以看成是一道全排列的问题

思路:

1、全排列出所有可能的数列(全排列问题留到稍后说)

2、对数列进行分析判断,其中:

        num = num1 + num2 / num3; 相当于是将数列进行截断,直接暴力截断是不可能的,时间实在太长了,参考别人的想法进行剪枝:

             (1)求num的长度为len,可知num1的长度不大于len

             (2)由于num是整数,所以num2 > num3(即 num2 长度大于等于余下长度的1/2) 且num2 % num3 == 0

                              (这个剪枝的想法参考于http://blog.csdn.net/skillart/article/details/8902478)

             (3)涉及将数字转化成字符串以及将区间内数字转化为一个整数


我第一次写的代码:

#include<stdio.h>  #include<stdlib.h>#include<string.h>long long aim;int n = 0;  int len = 0;// 数组区间转化为数字的函数 int getsum( int list[], int f, int e){ int sum = 0; int i; for( i = f; i <= e; i++){ sum = list[i] + sum * 10; } return sum; } // 判断函数  void judge( int* list){ int i, j, num1, num2, num3; for( i = 0; i < len; i++){ num1 = getsum( list, 0, i); for( j = i + ( 8 - i) / 2; j < 8; j++){ num2 = getsum( list, i+1, j); num3 = getsum( list, j+1, 8); if( num2 % num3){ continue; } else if( num1 + num2 / num3 == aim){ n++; } } } }  //交换函数 void swap(int* list, int a, int b)  {     int m;          m = list[a];          list[a] = list[b];          list[b] = m; //交换两个指针所指的数  }    //全排列函数 void perm(int list[], int k, int m)  {     int i;          if( k > m)     //两个指针      {                  judge(list);     }          else    //头指针大等于尾指针时      {                  for(i = k; i <= m; i++)                   {                          swap(list, k, i);                perm(list, k + 1, m);                          swap(list, k, i);                  }          }  }    int main()  {          int list[] = {1,2,3,4,5,6,7,8,9};     char str[10]; scanf("%I64d", &aim);  sprintf( str, "%d", aim); //啊啊,这里的sprintf是我第一次用,非常好用啊 len = strlen(str); perm(list, 0, 8);      printf("%d", n);          return 0;  }  

之后进行了一些算法的调整: 好像比较快了

// 判断函数  void judge( int* list){ int i, j, a, b, c; int blast; for( i = 0; i < len; i++){ a = getsum( list, 0, i); blast = (( aim - a) * list[8]) % 10; for( j =i+(8-i)/2; j < 8; j++)//从list数组中间位置开始找b末尾位置             {                                               if(list[j]==blast)            //找到b尾部                 {                      b = getsum(list, i+1, j); //将list数组中的[i+1-j]转化为数字,赋值给b                    c = getsum(list, j+1, 8); //将list数组中的[j+1-8]转化为数字,赋值给c                     if (b % c == 0 && a + b / c == aim)  //判断合理性 {                            ++n; }                     break;                  }              }       } }

我借此机会可以研究一下全排列。其实在2015预赛已经考过了,当初做出来那只是运气好,还是需要认真的研究一下的。

主要参考了这个作者的一些讲解,他讲的很清楚,我算是在做一个读后感

http://www.cnblogs.com/nokiaguy/archive/2008/05/11/1191914.html


对全排列的求法有两种:字典序法和递归分治法

【1】字典序法:

            字典序 1 2 3的全排列如下: 1 2 3 , 1 3 2 , 2 1 3 , 2 3 1 , 3 1 2 , 3 2 1

            设 123456 是字典序最小的序列,654321是字典序最大的序列,我们在寻找任一序列的字典序下一序列:如 124653

             (1)从右至左寻找某数满足:左侧数<右侧数,a 标记为左侧数 ( i = 4 )

             (2)从右至左寻找 i 前最早出现的某数大于 i ,b 标记为此数(i = 5)

             (3)将 j 和 i 交换 得 125643

             (4)将 i+1 及以后的数从小到大排列 得 125346

            如字典序下一个序列是 125346 【事实上就是在寻找比 124653 大的最小的数 ——总的来说最大的数上浮】

#include<stdio.h>void swap(int *a, int *b)                                    // 交换函数  {          int m;          m = *a;          *a = *b;          *b = m;  }void sort( int list[], int a, int b){                        // 冒泡排序int i, j;for( i = b; i > a; i--){for( j = a; j < i; j++){if( list[j] > list[j+1]){swap( &list[j], &list[j+1]);}}}} int main(){          int list[] = {1, 2, 3, 4};          int i, j, a, b, n = 24; while(n--){ for( i = 0; i < 4; i++)printf("%d ", list[i]); //打印排列printf("\n");  for( i = 3; i > 0; i--){               // 判断是否为(1)情况,标记a if( list[i-1] < list[i]){ a = i - 1; break; } } for( j = 3; j >= a; j--){              // 判断是否为(2)情况,标记bif( list[j] > list[a]){b = j;break;}}  swap(&list[a], &list[b]);              // 交换 sort( list, a + 1, 3);                 //执行(3) }        return 0;  }  

【2】递归分治法

这个想了比较久,多少还是懂了

在分析之前的那个方法之前,先分析一下刘汝佳书上的一种方法,比较简单易懂。基本上是这样的:

               (1)将待排列数列分成两列序列A,和集合S;(S不必存储,因为可以被A完全确定)

               (2)从小到大的顺序以此考虑S中的每一个元素V  { 若V已经被使用, 则跳过; 若V未被使用, 则使用,加入数组A,并标记}

               (3)当S中没有元素,则输出A

这是一种递归调用实现字典序的办法,其核心是一种 确定数列前缀的思想:A为前缀序列,依次将S中的数加入A,然后进行全排列。


之后更加简洁的方法就出现了,重点是这里

for(i = k; i <= m; i++)                  {                          swap(&list[k], &list[i]); //将K和i进行交换,k以前的数生成新的前缀                         perm(list, k + 1, m);      //进行全排列                    swap(&list[k], &list[i]);   //将k和i换回来,返回到上一层               } 

交换的过程相当于将K以前的数作为前缀,之后进行全排列
这个网址说的很清楚http://www.sjsjw.com/kf_code/article/21_12537_14709.asp。以下引用:

(注:(A)表示执行语句(A),见代码后标记。(B0)表示第0层递归,相当于上图中的根结点结点{1,2,3,4})
第一步:i = k = 0; 交换list[0]←→list[0](A),即将list[0]加入到前缀得到如结点(b)所示,然后对{2,3,4}全排列。(B0)(B0表示第0层递归,下同)
第二步:i = k = 1;交换list[1]←→list[1](A),即将list[1]加入到前缀得到如结点(c)所示,然后对{3,4}全排列。(B1)
第三步:i = k = 2;(进入Prim)交换list[2]←→list[2](A),即将list[2]加入到前缀得到如结点(f)所示,然后对{4}全排列。(B2)
第四步:i = 2,k = 3,(进入Prim)打印第三步的情况,即打印结点(f)。(B3)
第五步:i = k = 2,交换list[2]←→list[2](C),即还原到交换前状态。(B2)
第六步:i = 3, k = 2,(for循环)交换list[3]←→list[2](A),即将list[3]加入前缀得到如结点(g)所示,然后对{3}全排列。(B2)
第七步:i = 3,k = 3,(进入Prim)打印第六步的情况,即打印结点(g)。(B3)
第八步:i = 3, k = 2,交换list[3]←→list[2](C),即还原到交换前状态。(B2)
第九步:i = 4, k = 2,跳出for循环,第一个分支打印完毕,准备打印第二个分支。(B2->B1)
第十步:i = k = 1,交换list[1]←→list[1](C),即还原到交换前状态。(B1)
第十一步:i = 2, k = 1,交换list[2]←→list[1](A),即将list[2]加入前缀得到如结点(d)所示,然后对{2,4}全排列。(B1)
第十二步:i = 2, k = 2,(进入Prim)交换list[2]←→list[2](A),即将list[2]加入到前缀得到如结点(h)所示,然后对{4}全排列。(B2)
第十三步:i = 2,k = 3,(进入Prim)打印第十二步的情况,即打印结点(h)。(B3)
......................
不一一列举了,(i)就相当于交换到第几个了,(k)相当于在第几层。
好了,全排列就讲到这里了




0 0
原创粉丝点击