2013蓝桥杯c组c/c++真题:带分数

问题描述
100 可以表示为带分数的形式:100 = 3 + 69258 / 714。
还可以表示为:100 = 82 + 3546 / 197。
注意特征:带分数中,数字1~9分别出现且只出现一次(不包含0)。
类似这样的带分数,100 有 11 种表示法。
输入格式
从标准输入读入一个正整数N (N<1000*1000)
输出格式
程序输出该数字用数码1~9不重复不遗漏地组成带分数表示的全部种数。
注意:不要求输出每个表示,只统计有多少表示法!
样例输入1
100
样例输出1
11
样例输入2
105
样例输出2
6

解题思路

由于题目要求数字1-9分别出现一次且只出现一次,那么我们首先想到的就是全排列数字1-9,针对每一个全排列,遍历放置+和/的位置,如果遍历到使a+b/c==n 且 b%c==0的结果,就让答案个数+1.

做题技巧

对于蓝桥题目说是1~9不重复,给出一个条件,大概率用全排列函数可以解决的。

并且这里我们可以运用next_permutation()函数进行全排列。

next_permulation

使用该函数时需要头文件<algorithm>

使用方法:next_permutation(数组头地址,数组尾地址);若下一个排列存在,则返回真,如果不存在则返回假。

例如数组名为num,next_permutation(num,num+n)函数是对数组num中的前n个元素进行全排列,同时并改变num数组的值。

算法模板
int a[10] = {1,2,3,4,5,6,7,8,9},sum=0;
   while(next_permutation(a,a+9))
   {
   }
真题举例-算式问题
本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。
看这个算式:
☆☆☆ + ☆☆☆ = ☆☆☆
如果每个五角星代表 1 ~ 9 的不同的数字。
这个算式有多少种可能的正确填写方法?
#include <bits/stdc++.h>
using namespace std;
int main()
{
   int a[10] = {1,2,3,4,5,6,7,8,9},sum=0;
   while(next_permutation(a,a+9))
   {
       int q,w,e;
       q=a[0]*100+a[1]*10+a[2];
       w=a[3]*100+a[4]*10+a[5];
       e=a[6]*100+a[7]*10+a[8];
       if(q+w==e)
       {
           sum++;
       }
   }
   cout<<sum;
   return 0;
}
真题举例-凑算式
本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。
A+B/C+DEF/GHI=10
这个算式中 A ~ I 代表 1 ~ 9 的数字,不同的字母代表不同的数字。
(式子改成:a+b/c+m/n=10,其中,a,b,c都是一位数,m,n都是三位数。总共10个数字,并且这些数是1~9.不能重复)
比如:
6+8/3+952/714 就是一种解法,
5+3/1+972/486 是另一种解法。
这个算式一共有多少种解法?

思路:全排列用起来,还有一点,这道题通分很关键。

至于为什么需要通分,这里我们可以对题目给出的实例进行分析:

例如6+8/3+952/714,若不通分,8/3和952/714都除不尽,这样除之后的小数相加,不能准确的等于10.

另外符合(bn+cm)/cn=10-a(a+b/c+m/n=10)表达式的a、b、c、m、n这五个数在进行(bn+cm)/cn运算操作的时候,结果肯定是个整数,因为10和a都为整数,那么相减必然为整数,而(bn+cm)/cn的结果等于10-a;但是我们不排除不满足(bn+cm)/cn=10-a该等式时(bn+cm)/cn是一个小数,且很有可能本身(bn+cm)/cn!=10-a,但是由于(bn+cm)/cn声明的类型是int类型,会进行小数下取整操作,取整后的整数值等于10-a的整数值。

所以,为保证答案不被下取整,我们需要将参与运算的数据声明为double类型,或者将除运算左右的结果强制转换成double类型。

#include <bits/stdc++.h>
using namespace std;
int main()
{  
   int a[10] = {1,2,3,4,5,6,7,8,9},sum=0;
   
   //注意这里使用do while循环 即先循环一次,再执行while循环体的条件
   //使用while会跳过最开始的123456789这种情况
   do{
       //关键地方:声明为double类型
       //1/5的结果为0,C++会默认为两个整数相除,整数相除取整部分就是0。
       //所以要写成1/5.0,C++就会识别为浮点数,默认结果为double类型,相除结果自然就是一个小数。
       double xx,xxx;
       xx=a[3]*100+a[4]*10+a[5];
       xxx=a[6]*100+a[7]*10+a[8];

       //printf("%lf\n",(xx*a[2]+a[1]*xxx)/(xxx*a[2]));
       //另一个关键的地方 这里需要进行通分
       if((xx*a[2]+a[1]*xxx)/(xxx*a[2])==10-a[0])
       {
           sum++;
       }
   }while(next_permutation(a,a+9));
   cout<<sum;
   return 0;
}
#include <bits/stdc++.h>
using namespace std;
int main()
{             //a,b,c,d,e,f,g,h,i
              //0,1,2,3,4,5,6,7,8
   int a[10] = {1,2,3,4,5,6,7,8,9},sum=0;
   
   do{
       int xx,xxx;
       xx=a[3]*100+a[4]*10+a[5];
       xxx=a[6]*100+a[7]*10+a[8];

        //在做除法运算时进行double强制类型转换
       if(double(xx*a[2]+a[1]*xxx)/double(xxx*a[2])==10-a[0])
       {
           sum++;
       }
   }while(next_permutation(a,a+9));
   cout<<sum;
   return 0;
}

另外若将数据类型声明为Int类型,我们可以添加一个判断条件(xx*a[2]+a[1]*xxx)%(xxx*a[2])==0,这样即可排除下取整后等于10-a的结果了。

#include <bits/stdc++.h>
using namespace std;
int main()
{  
   int a[10] = {1,2,3,4,5,6,7,8,9},sum=0;
   
   do{
       //关键地方:声明为double类型
       //1/5的结果为0,C++会默认为两个整数相除,整数相除取整部分就是0。
       //所以要写成1/5.0,C++就会识别为浮点数,默认结果为double类型,相除结果自然就是一个小数。
       int xx,xxx;
       xx=a[3]*100+a[4]*10+a[5];
       xxx=a[6]*100+a[7]*10+a[8];

       //另一个关键的地方 这里需要进行通分
       if((xx*a[2]+a[1]*xxx)/(xxx*a[2])==(10-a[0]) && (xx*a[2]+a[1]*xxx)%(xxx*a[2])==0)
       {
           sum++;
       }
   }while(next_permutation(a,a+9));
   cout<<sum;
   return 0;
}

相信经过上述实例的讲解,大家对next_permutation和全排列的使用场景有所体会,接下来我们来看带分数这道题的代码。

注意带分数题目的要求与凑算式的不同之处,凑算式已经明确规定了等式出现的五个数据分别是几位数。而带分数位数并没有明确确定,位数不一定。

带分数代码

#include <iostream>
#include <algorithm>
using namespace std;

/*代码思路
利用next_permutation函数求全排列
并针对所有的全排列将+与/遍历插入
插入过程中发现满足等式的情况
就将答案++
*/
//全局变量
int num[9]={1,2,3,4,5,6,7,8,9};

int parse(int l,int r){
    int res=0;
    while(l<=r){
        res=res*10+num[l];
        l++;
    }
    return res;
}

int main()
{
    int n;
    cin>>n;
    int ans=0;
    
    do{
        //寻找a
        //由于三个数任意一个均不能为空 所以a的位数最多为7位 b、c至少一位数
        for(int i=0;i<7;i++){
            int a=parse(0,i);
            //printf("a:%d\n",a);
            //对代码的优化 若当前位数的a已经大于n 则不必遍历后面的位数 因为增加位数只会使a越来越大于n 即没有满足等式的答案
            if(a>=n) break;
            for(int j=i+1;j<8;j++){
                int b=parse(i+1,j);
                //printf("b:%d\n",b);
                int c=parse(j+1,8);
                //printf("c:%d\n",c);
                if(a+b/c==n && b%c==0){
                    ans++;
                }
            }
        }
    }while(next_permutation(num,num+9));
    
    cout<<ans<<endl;
    return 0;
}

分析一下时间复杂度 ,while循环中嵌套了两个for循环,时间复杂度是O(n*);(家人们这个时间复杂度,我不太确定,求大佬指点);

即为两层for循环的时间复杂度,9个数,8个空隙中找两个位置放+和/运算符。

带分数另一解题思路

在介绍另一种思路之前,我们先来介绍一下递归求全排列。

递归求全排列

思路比较简单,即依次枚举每个位置放哪个数,记住将递归都转换为递归搜索树,方便理解。

这里给出递归搜索树:

同时这里给出时间复杂度的证明,最终该递归求全排列的时间复杂度为O(n*n!);

根据图示递归搜索树,我们的递归求全排列代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

const int N=10;
int path[N];
bool st[N];
int n;

void dfs(int u)
{
    //边界条件
    if(u>n){
        for(int i=1;i<=n;i++){
            printf("%d ",path[i]);
        }
        puts("");
        return;
    }
                                                                                              
    for(int i=1;i<=n;i++){
        if(!st[i]){
            st[i]=true;
            path[u]=i;
            dfs(u+1);
            st[i]=false;//恢复现场
            path[u]=0;//恢复现场
        }
    }
}

int main()
{
    cin>>n;
    
    dfs(1);
    
    return 0;
}
利用递归求全排列解带分数问题
#include <iostream>
using namespace std;

const int N=15;
int state[N];
//st判断有没有重复
bool st[N];
int n;
int ans;

int parse(int l,int r)
{
    int res=0;
    while(l<=r){
        res=res*10+state[l];
        l++;
    }
    return res;
}

void dfs(int u)
{
    if(u>9){
        for(int i=1;i<=7;i++){
            int a=parse(0,i);
            //如果a已经大于n 就直接break
            if(a>=n) break;
            for(int j=i+1;j<=8;j++){
                int b=parse(i+1,j);
                int c=parse(j+1,9);
                if(a+b/c==n && b%c==0){
                    ans++;
                }
            }
        }
    }
    
    for(int i=1;i<=9;i++){
        if(!st[i]){
            st[i]=true;
            state[u]=i;
            dfs(u+1);
            //恢复现场
            st[i]=false;
            state[u]=0;
        }
    }
}

int main()
{
    scanf("%d",&n);
    dfs(1);
    cout<<ans<<endl;
    return 0;
}

该算法的时间复杂度为O(n*n!*);

优化

我们可以发现上述两种解决方式的代码,都是枚举了a,b,c三个数的全排列,而时间复杂度是和递归层数成正比的,因此,我们为了优化代码,可以只枚举a和c两个数的全排列,通过等式,将b算出来即可。

我们有等式a+b/c==n;那么同乘c,即可得到ac+b=nc ---> b=nc-ac;

举个例子来进一步理解一下优化的原理,在原来的方案,我们需要枚举三个数a,b,c;那么当a,c枚举完后,用来枚举b的数字还剩下三个,那么我们枚举b就需要6种方案;但是现在我们优化后,只需要枚举a,c,直接计算b即可,那么也就是说b的方案就是确定的一种,相比之下,减少了5个方案的枚举时间。

思路

① dfs_a(int a);利用该函数枚举a的排列,参数表示当前数据a的数值大小;

② dfs_c(int u,int a,int c);枚举变量c的值,参数表示当前数据a的数值大小和c的数值大小;

③ check(int a,int c);通过a,c计算b的数值,并检查b中用到的数字和a和c是否重复,以及检查1-9的数字是否都用过一遍。

优化代码
#include <iostream>
#include <cstring>
using namespace std;

const int N=10;
//st用来判重 backup用来检查1-9是否都用了一遍 以及b中的数字是否和a和c有重复的数字
//全局变量初始值一定为0  局部变量没初始化 那么初始值是随机的
bool st[N],backup[N];
int n;
int ans;

bool check(int a,int c)
{
    //a+b/c==n;
    //b=nc-ac;
    //其中1≤N<106,因此n最多为6位数,c有可能为8位数字,这样有可能爆int,因此强制类型转换为long long
    long long b=n*(long long)c-a*c;
    //有一个为0 就不满足条件
    if(!a || !b || !c) return false;
    //因为st还需要在枚举时进行判重 不能修改 所以将st拷贝至backup数组
    memcpy(backup,st,sizeof st);
    //判断b中的数字和a中的数字和c中的数字是否重复
    while(b){
        //取出b的个位数字
        int x=b%10;
        //只要b中有数字等于0 或者和a和c中的数字有重复 就不满足条件
        if(!x || backup[x]) return false;
        backup[x]=true;
        b/=10;
    }
    
    //判断1-9是否都用了一次
    for(int i=1;i<=9;i++){
        //只要有一个数没用过 就不满足条件
        if(!backup[i]) return false;
    }
    
    return true;
}

void dfs_c(int a,int c)
{
    //进入check函数 计算b的值 并判断数字1-9是否用且只用了一次
    if(check(a,c)) ans++;
    //这个函数不需要进行边界条件判定
    //至于递归必须要有一个结束窗口
    //该函数在st数组全为true时,就自动返回了 因为没有进入新的dfs_c函数
    for(int i=1;i<=9;i++)
    {
        if(!st[i]){
            st[i]=true;
            dfs_c(a,c*10+i);
            st[i]=false;//恢复现场
        }
    }
}

void dfs_a(int a)
{
    //边界条件 如果a的值已经大于等于n 那么b和c为空 不符合条件 直接结束
    if(a>=n) return;
    //如果a不为0 那么就枚举c的数值 只能是数字1-9 0需要排除 所以出现0的时候 就不用枚举了
    if(a) dfs_c(a,0);
    
    //枚举a
    for(int i=1;i<=9;i++){
        if(!st[i]){
            st[i]=true;
            dfs_a(a*10+i);
            st[i]=false;//恢复现场
        }
    }
}

int main()
{
    cin>>n;
    //枚举a 参数为当前a的数值大小
    dfs_a(0);
    
    cout<<ans<<endl;
    
    return 0;
}

总结

带分数这道题利用前两种方案,都是比较好理解的,最后一个方案,应用了dfs的嵌套,枚举a后枚举c,最后直接计算出b;思路在解题过程,已经讲解的比较清楚,但在时间复杂度这一块把握的不是很好,这里总结一下明确的时间复杂度:next_permutation的时间复杂度为O(n),n代表传入的需要进行全排列的数字个数;dfs递归求全排列的时间复杂度为O(n*n!).

猜你喜欢

转载自blog.csdn.net/weixin_54106682/article/details/128688493