Questão real do Grupo C da Copa Lanqiao 2013 c/c++: Frações mistas

Descrição do problema
100 pode ser expresso como um número misto: 100 = 3 + 69258/714.
Também pode ser expresso como: 100 = 82 + 3546/197.
Observe as características: Nos números mistos, os números de 1 a 9 aparecem cada um e apenas uma vez (excluindo 0).
Existem 11 maneiras de representar números mistos como este, 100.
O formato de entrada
lê um número inteiro positivo N (N<1000*1000) da entrada padrão. O programa de
formato de saída gera o número usando os dígitos de 1 a 9 para formar todos os números representados por frações mistas sem repetição ou omissão. Nota: Não é necessário gerar cada representação, apenas conte quantas representações existem! Exemplo de entrada 1 100 Exemplo de saída 1 11 Exemplo de entrada 2 105 Exemplo de saída 2 6









Ideias para resolução de problemas

Como a questão exige que os números de 1 a 9 apareçam uma e apenas uma vez, a primeira coisa que pensamos é organizar os números de 1 a 9 por completo. Para cada arranjo completo, percorra as posições onde + e / são colocados. Se o a travessia atinge a+b/ O resultado de c==n e b%c==0 é aumentar o número de respostas em 1.

Habilidades para resolver problemas

Para a questão da Ponte Azul, diz-se que 1 a 9 não se repetem e é dada uma condição que pode ser resolvida com alta probabilidade usando a função de permutação completa.

E aqui podemos usar a função next_permutation() para realizar a permutação completa.

próxima_permulação

O arquivo de cabeçalho <algoritmo> é necessário ao usar esta função

Uso: next_permutation (endereço principal do array, endereço final do array); se a próxima permutação existir, retorne verdadeiro, se não, retorne falso.

Por exemplo, o nome do array é num, e a função next_permutation(num,num+n) organiza completamente os primeiros n elementos do array num e altera o valor do array num ao mesmo tempo.

modelo de algoritmo
int a[10] = {1,2,3,4,5,6,7,8,9},sum=0;
   while(next_permutation(a,a+9))
   {
   }
Exemplos de questões de teste reais - problemas aritméticos
Esta questão é uma questão de preencher as lacunas. Você só precisa calcular o resultado e usar a instrução de saída no código para gerar o resultado preenchido.
Veja esta fórmula:
☆☆☆ + ☆☆☆ = ☆☆☆
Se cada estrela de cinco pontas representa um número diferente de 1 a 9.
Quantas maneiras corretas possíveis existem para preencher esta equação?
#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;
}
Exemplos de questões de teste reais - fórmulas de cálculo
Esta questão é uma questão de preencher as lacunas. Você só precisa calcular o resultado e usar a instrução de saída no código para gerar o resultado preenchido.
A+B/C+DEF/GHI=10
Nesta fórmula, A ~ I representa números de 1 a 9, e letras diferentes representam números diferentes.
(A fórmula é alterada para: a+b/c+m/n=10, onde a, b, c são todos números de um dígito, m, n são todos números de três dígitos. Existem 10 números no total, e esses números são de 1 a 9. Não podem ser repetidos)
Por exemplo:
6+8/3+952/714 é uma solução,
5+3/1+972/486 é outra solução.
Quantas soluções existem para esta equação?

Idéia: Use todos os arranjos, e mais uma coisa, a pontuação geral dessa questão é muito importante.

Quanto ao motivo pelo qual são necessárias pontuações comuns, podemos analisar aqui os exemplos dados na pergunta:

Por exemplo, 6+8/3+952/714, se não forem divisíveis, nem 8/3 nem 952/714 podem ser divididos completamente, portanto a soma dos decimais após a divisão não pode ser exatamente igual a 10.

Além disso, os cinco números a, b, c, m e n que estão em conformidade com a expressão (bn+cm)/cn=10-a (a+b/c+m/n=10) estão em andamento (bn +cm)/ Quando cn é operado, o resultado deve ser um número inteiro, porque tanto 10 quanto a são números inteiros, então a subtração deve ser um número inteiro, e o resultado de (bn+cm)/cn é igual a 10-a; mas não descartamos que (bn+ cm)/cn=10-a Quando (bn+cm)/cn é um decimal, é muito provável que (bn+cm)/cn!=10-a em si, mas como (bn+cm)/ cn O tipo declarado é do tipo int, e o arredondamento decimal será realizado. O valor inteiro arredondado é igual ao valor inteiro de 10-a.

Portanto, para garantir que a resposta não seja arredondada para baixo, precisamos declarar os dados envolvidos na operação como tipo double, ou forçar os resultados da operação de divisão para tipo 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;
}

Além disso, se o tipo de dados for declarado como tipo Int, podemos adicionar uma condição de julgamento (xx*a[2]+a[1]*xxx)%(xxx*a[2])==0 , que pode excluir o seguinte Após arredondamento, é igual ao resultado de 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;
}

Acredito que após a explicação dos exemplos acima, todos tenham alguma compreensão dos cenários de uso de next_permutation e permutação completa. A seguir, vamos dar uma olhada no código para a questão dos números mistos.

Preste atenção à diferença entre os requisitos para questões de números mistos e a fórmula de reposição. A fórmula de reposição especificou claramente os dígitos dos cinco dados que aparecem na equação. O número de dígitos em decimais mistos não é claramente determinado e o número de dígitos não é necessariamente certo.

código de fração mista

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

Analise a complexidade do tempo. Existem dois loops for aninhados no loop while, e a complexidade do tempo é O(n* ); (não tenho certeza sobre essa complexidade de tempo, por favor, me dê alguns conselhos);

Essa é a complexidade de tempo de um loop for de dois níveis, 9 números, e encontre duas posições nas 8 lacunas para colocar os operadores + e /.

Outra maneira de resolver problemas com números mistos

Antes de apresentar outra ideia, vamos primeiro apresentar o arranjo total recursivo.

Pesquisa recursiva para arranjo total

A ideia é relativamente simples, ou seja, enumerar qual número colocar em cada posição por vez, e lembrar de converter a recursão em uma árvore de busca recursiva para facilitar o entendimento.

Aqui está uma árvore de pesquisa recursiva:

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

Acho que você gosta

Origin blog.csdn.net/weixin_54106682/article/details/128688493
Recomendado
Clasificación