2013 Lanqiao Cup Group C c/c++ real question: Mixed fractions

Problem Description
100 can be expressed as a mixed number: 100 = 3 + 69258 / 714.
It can also be expressed as: 100 = 82 + 3546 / 197.
Note the characteristics: In mixed numbers, the numbers 1 to 9 appear each and only once (excluding 0).
There are 11 ways to represent mixed numbers like this, 100.
The input format
reads a positive integer N (N<1000*1000) from the standard input. The
output format
program outputs the number using the digits 1 to 9 to form all numbers represented by mixed fractions without repetition or omission.
Note: It is not required to output each representation, only count how many representations there are!
Sample input 1
100
Sample output 1
11
Sample input 2
105
Sample output 2
6

Problem-solving ideas

Since the question requires the numbers 1-9 to appear once and only once, the first thing we think of is to arrange the numbers 1-9 in full. For each full arrangement, traverse the positions where + and / are placed. If the traversal reaches a+b/ The result of c==n and b%c==0 is to increase the number of answers by 1.

Question skills

For the Blue Bridge question, it is said that 1 to 9 are not repeated, and a condition is given, which can be solved with a high probability using the full permutation function.

And here we can use the next_permutation() function to perform full permutation.

next_permulation

The header file <algorithm> is required when using this function

Usage: next_permutation (array head address, array tail address); if the next permutation exists, return true, if not, return false.

For example, the array name is num, and the next_permutation(num,num+n) function completely arranges the first n elements in the array num and changes the value of the num array at the same time.

algorithm template
int a[10] = {1,2,3,4,5,6,7,8,9},sum=0;
   while(next_permutation(a,a+9))
   {
   }
Examples of real test questions-arithmetic problems
This question is a fill-in-the-blank question. You only need to calculate the result and use the output statement in the code to output the filled-in result.
Look at this formula:
☆☆☆ + ☆☆☆ = ☆☆☆
If each five-pointed star represents a different number from 1 to 9.
How many possible correct ways are there to fill in this equation?
#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;
}
Examples of real test questions - calculation formulas
This question is a fill-in-the-blank question. You only need to calculate the result and use the output statement in the code to output the filled-in result.
A+B/C+DEF/GHI=10
In this formula, A ~ I represent numbers from 1 to 9, and different letters represent different numbers.
(The formula is changed to: a+b/c+m/n=10, where a, b, c are all one-digit numbers, m, n are all three-digit numbers. There are 10 numbers in total, and these numbers are 1 ~9. Cannot be repeated)
For example:
6+8/3+952/714 is one solution,
5+3/1+972/486 is another solution.
How many solutions are there to this equation?

Idea: Use all arrangements, and one more thing, the general score for this question is very important.

As for why common scores are needed, here we can analyze the examples given in the question:

For example, 6+8/3+952/714, if they are not divisible, neither 8/3 nor 952/714 can be divided completely, so the sum of the decimals after division cannot accurately equal 10.

In addition, the five numbers a, b, c, m, and n that conform to the expression (bn+cm)/cn=10-a (a+b/c+m/n=10) are in progress (bn+cm)/ When cn is operated, the result must be an integer, because both 10 and a are integers, so the subtraction must be an integer, and the result of (bn+cm)/cn is equal to 10-a; but we do not rule out that (bn+ cm)/cn=10-a When (bn+cm)/cn is a decimal, it is very likely that (bn+cm)/cn!=10-a itself, but since (bn+cm)/ cn The declared type is int type, and decimal rounding will be performed. The rounded integer value is equal to the integer value of 10-a.

Therefore, in order to ensure that the answer is not rounded down, we need to declare the data involved in the operation as double type, or force the results of the division operation to double type.

#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;
}

In addition, if the data type is declared as Int type, we can add a judgment condition (xx*a[2]+a[1]*xxx)%(xxx*a[2])==0 , which can exclude the following After rounding, it equals the result of 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;
}

I believe that after the explanation of the above examples, everyone has some understanding of the usage scenarios of next_permutation and full permutation. Next, let’s look at the code for the question of mixed numbers.

Pay attention to the difference between the requirements for mixed-number questions and the make-up formula. The make-up formula has clearly specified the digits of the five data appearing in the equation. The number of digits in mixed decimals is not clearly determined, and the number of digits is not necessarily certain.

mixed fraction code

#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;
}

Analyze the time complexity. There are two for loops nested in the while loop, and the time complexity is O(n* ); (I’m not sure about this time complexity, please give me some advice);

That is the time complexity of a two-level for loop, 9 numbers, and find two positions in the 8 gaps to put the + and / operators.

Another way to solve problems with mixed numbers

Before introducing another idea, let's first introduce the recursive total arrangement.

Recursive search for total arrangement

The idea is relatively simple, that is, enumerate which number to put in each position in turn, and remember to convert the recursion into a recursive search tree for easy understanding.

Here is a recursive search tree:

同时这里给出时间复杂度的证明,最终该递归求全排列的时间复杂度为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!).

Guess you like

Origin blog.csdn.net/weixin_54106682/article/details/128688493