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:
![](https://img-blog.csdnimg.cn/img_convert/5c27d42b3a00ca670c1cc44b7bdf74a9.png)
同时这里给出时间复杂度的证明,最终该递归求全排列的时间复杂度为O(n*n!);
![](https://img-blog.csdnimg.cn/img_convert/9d4c90bac2901f94ad8d172fca9c5848.png)
根据图示递归搜索树,我们的递归求全排列代码如下:
#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!).