带分数
题目描述
题意解释
给定一个数 n n n,问有多少组 a , b , c a,b,c a,b,c,满足 a + b c = n a+\dfrac {b}{c}=n a+cb=n,并且 a , b , c a,b,c a,b,c不重不漏的涵盖了1到9的数字。注意: a , b , c a,b,c a,b,c中都是不含有0的!!!
核心思路
解法一:
- 暴力枚举出这9个数的全排列,用一个数组num来保存全排列的结果
- 从全排列的结果中用两重循环暴力分解出三段,每段代表一个数,即分隔出的这三段代表a,b,c
- 验证枚举出来的三个数是否满足题干条件,若满足则计数
为什么i下标i=0,i<7而不是i=0,i<9呢?为什么j下标是j=i+1,j<8,而不能是j=i+1,j<9呢?
我们要注意这里的i和j并不是数字1到9,而是区间的下标。我们得到一个9位的全排列,要在这个全排列中找出三段来作为a,b,c。对于第一段来说,它最多有7位,假设它有8位,那么还只剩下一位,那么这一位只能成为一段了,那么结果就只有两段,而不是分隔成三段;假设它有9位,那么这一段都占满了9位,那就没有给第二段和第三段了,那么结果就只有一段,而不是分隔成三段。因此,第一段最多有7位。
固定好第一段之后,第二段必须从i+1开始而不能从i开始,因为如果第二段也从i开始,那么第一段的num[i]和第二段的num[i]就会相同,这就不符合题意了。对于第二段来说,当j=7时,表示第8位,如果我们写成j<9,那么当j=8时,就表示第9位,那么此时就没有了第三段,因此只有两段。因此,j<8而不是j<9。
解法二:
n = a + b c n=a+\dfrac {b}{c} n=a+cb,等式两边同时乘以c,得到 n c = a c + b nc=ac+b nc=ac+b,由于 n n n是题目输入的,因此 n n n是已知的。那么我们只需要枚举 a , c a,c a,c,就可以算出 b b b了,这样就不同枚举 b b b了。
大体步骤:
- 通过公式的转化,将枚举三个变量 a , b , c a,b,c a,b,c转换成枚举两个变量 a , c a,c a,c(因为第三个变量 b b b可以计算出来)
- 枚举 a
- 对于已经枚举出来的确定的每个a,再接着枚举c
- 对于每个枚举的a和c来说,我们来判断一下b是否成立就可以了
详细步骤:
- 我们先写一个dfs_a函数,先dfs一下a,它其实是一个排列,枚举一个排列,1,2,3,4, ⋯ \cdots ⋯,其实dfs_a函数就是再枚举a,可以得到一个确定的a,确定好a之后,然后在dfs_a的内部,在每一个叶子节点(也就是确定a是啥的那个结点)的时候,因为每一个叶子就代表每一个a的可能成立的方案,那么在a的每一个叶子节点的地方,我们都需要接着再枚举一下c,也就是我们要把a的每一个节点扩展成一个搜索树(即在a的每一个叶节子点的时候dfs一下c),叶子上的每一棵树都是对c的搜索,因此我们这里其实是dfs的一个嵌套关系,即在一个dfs的过程中,把它的叶节点再dfs一下, 所以我们需要在dfs搜索a的时候在a的叶节点的基础上搜索一下对应的c是多少,搜完c之后的话我们再去判断一下b就可以了,也就是在c的每一个叶子节点上判断一下b就可以了。
- 开一个判重数组st,用来记录1到9中的某个数字是否已经被使用过了,比如 s t [ 8 ] = t r u e st[8]=true st[8]=true表示数字8已经被使用过了, s t [ 3 ] = f a l s e st[3]=false st[3]=false表示数字3还没有被使用过。首先dfs一下a,第一个参数u表示我们当前已经用了多少个数字(总共有9个数字可以用),第二个参数表示a是多少,首先判断一下,如果 a>=n 了,由等式可知此时的a是不合法的,那么就不满足条件了,那就直接return了。如果a是可行的,那么我们就dfs一下c。
- 对于dfs_a,然后我们来枚举一下当前这个位置能用哪个数字,从1到9进行循环,看看可以用1到9中的哪个数字,如果当前这个位置上的数没有被用过的话,那么我们就可以用它,并做上标记,表示这个数字已经被使用过了,然后我们再递归到下一层,同时更新一下a,传到下一层的参数应该是u+1(因为上一层已经使用一个数字了嘛),然后a应该变成 a*10+i (比如原来前面层枚举得到a=12,然后在上一层枚举可以放数字3,那么当进入该层时,a应该被更新为 12 × 10 + 3 = 123 12\times 10+3=123 12×10+3=123),然后最后要记得恢复现场,因为这里存在一个回溯。
- dfs_c的话有三个参数,dfs_c的第一个参数u表示我们当前已经用了多少个数(总共有9个数字可以用),第二个参数是已经确定的a的值,因为我们要利用a的值,第三个参数代表当前c的值。如果u>9,说明我们已经用完了1到9中的这9个数字了,题目说了a,b,c “不重不漏地使用1到9中的这9个数字”,因此当a,b,c已经不重不漏地使用完这9个数字,那么就可以return了。否则的话那我们就可以check一下,如果说check成功,那我们的答案就++。check函数就是用来判断一下a和c是否满足要求,在已经a和c的情况下,求出b,然后看看这一组a,b,c是否满足要求。否则的话我们就从1到9枚举一下c,如果这个数没被用过的话,那我们就把它标记一下,并把它放在c的后面,再递归下一层,下一层的第一个参数就是u+1,第二个参数是a,第三个参数是c*10+i,还有记得恢复现场。
- 最后再实现一下这个check函数。函数的第一个参数就是a,第二个参数就是c,我们来判断一下a和c是不是满足要求的。那判断a和c是否满足要求,我们需要先利用 b=nc-ac把b计算出来,然后我们再对b的每一位数字分析,第一点看一下它有没有和a和c有相同的数字冲突,第二点看一下a,b,c是否已经把9个数字全部用完了。那这里的话我们可以搞一个判重数组backup,我们先把原来的那个数组copy过来,因为我们要对这个判重数组进行修改,但是我们原来的判重数组st是需要保证其是原样的,因为它需要恢复现场(回溯),因此把原来的st拷贝给backup,让backup去操作,这样就不会影响到st,那么恢复现场时st仍然是最初的状态。然后我们来取出b的每一位进行判断,在判断的过程中,如果b的某位是0或者b的某位已经出现过了,那就说明出错了,直接return false。否则我们给它标记一下它已经用过了,然后我们最后再去遍历一下数组中从1到9的每个数字是否已经被用了,也就是用a和c求出了b后,我们需要看看是否1到9中的每个数字都被使用到了嘛?如果判断过程中存在没有被用过的数字,那说明不满足条件,直接return false。当然了这个里面的话我们可以提前判断一些边界,这个题目中a和b和c都不能为0,所以我们可以在check的一开始判断一下,a或b或c中是否存在0,如果存在,直接return false,这样就可以做到一个小小的优化以及边界的判定。如果上述情况都满足,那就最后return true。
代码
解法一代码:可以AC,但是时间较长
#include<iostream>
using namespace std;
const int N=10;
int num[N]; //用来存储全排列的数字
bool used[N]; //用来判断1到9中的某个数字是否已经被使用过了
int n;
int ans;//答案
//计算出区间[l,r]内的这个十进制数
int cal(int l,int r)
{
int res=0;
for(int i=l;i<=r;i++)
{
res=res*10+num[i];
}
return res;
}
//u表示当前枚举到第几个位置
void dfs(int u)
{
//u=0表示在枚举第一个位置,u=8表示在枚举第8个位置
//从u=0到u=8一共枚举了9个位置,因此可以得到一个9位的全排列
//当u=9时再枚举第10个位置,说明我们已经枚举完了9个位置
if(u==9)
{
//第一段的区间是[0,7)
for(int i=0;i<7;i++)
{
//第二段的区间是[i+1,8)
for(int j=i+1;j<8;j++)
{
int a=cal(0,i); //第一段区间中的十进制数
int b=cal(i+1,j); //第二段区间中的十进制数
int c=cal(j+1,8); //第三段区间中的十进制数
//注意判断条件,因为C++中除法是整除,所以要转化为加减乘来计算
if(a*c+b==n*c)
ans++;
}
}
return; //回溯
}
// 可以用1到9这9个数字,依次枚举一下看看可以用哪个数字
for(int i=1;i<=9;i++)
{
//如果当前i这个数字还没有被使用过,则可以用i这个数字
if(!used[i])
{
used[i]=true;//标记i这个数字已经被用过了
num[u]=i; //在u这个位置上放i这个数字
//递归下一个位置
dfs(u+1);
//恢复现场
num[u]=0; //原来u这个位置上放的数字是0
used[i]=false;//原来i这个数字还没有被使用过
}
}
}
int main()
{
scanf("%d",&n);
//从第0个位置开始
dfs(0);
printf("%d\n",ans);
return 0;
}
解法二代码:可以AC,时间较快
#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
const int N=10;
//st数组用来判断1到9中的某个数字是否已经被使用过,st[i]=true表示数字i已经被使用过了,st[i]=false表示数字i还没有被使用过
bool st[N],backup[N];
int n;
int ans; //答案
bool check(int a,int c)
{
//把st的状态拷贝到backup数组中,这样st数组就会保持原来的状态
memcpy(backup,st,sizeof st);
LL b=n*(LL)c-a*c; //已知了a和c,用公式计算出b的值
//由于题目说了a,b,c都不会为0
if(!a||!b||!c)
return false;
//依次取出b的每一位
while(b)
{
int x=b%10;
b/=10;
//如果b的某一位存在0或者该位上的数字已经出现过了,则不合法
if(!x||backup[x])
return false;
backup[x]=true; //标记该位上的数字x已经被使用过了
}
//a,b,c都已经确定了。这里判断是否a,b,c都已经 不重不漏地用完了1到9这9个数字
for(int i=1;i<=9;i++)
{
//因为题目要求a,b,c要不重不漏地用完1到9这9个数字,因此,如果此时还存在没有被使用的数字,则不合法
if(!backup[i])
return false;
}
//能到这里,说明该组a,b,c是合法的
return true;
}
//u表示当前已经用了多少个数字
void dfs_c(int u,int a,int c)
{
//由于我们可以用1到9这9个数字,因此如果u>9,说明我们已经用完了这9个数字,则可以return了
if(u>9) //这里写u==9也可以ac
return;
//判断一下已知的a,c,所构造出的b,即a,b,c这一组是否合法,如果合法,则答案+1
if(check(a,c))
ans++;
//可以用1到9这9个数字,依次枚举一下看看可以用哪个数字
for(int i=1;i<=9;i++)
{
//如果当前i这个数字还没有被使用过,则可以用i这个数字
if(!st[i])
{
st[i]=true; //标记i这个数字已经被用过了
//递归到下一层
dfs_c(u+1,a,c*10+i);
//恢复现场
st[i]=false;//标记i这个数字还没有被用过
}
}
}
//u表示当前已经用了多少个数字
void dfs_a(int u,int a)
{
//如果枚举的这个a已经大于了n,则可以return了
if(a>=n)
return;
//由于题目说了a不为0,因此当a不为0时就可以进入递归c
if(a)
dfs_c(u,a,0);
//可以用1到9这9个数字,依次枚举一下看看可以用哪个数字
for(int i=1;i<=9;i++)
{
//如果当前i这个数字还没有被使用过,则可以用i这个数字
if(!st[i])
{
st[i]=true;//标记i这个数字已经被用过了
//递归到下一层
dfs_a(u+1,a*10+i);
//恢复现场
st[i]=false;//标记i这个数字还没有被用过
}
}
}
int main()
{
scanf("%d",&n);
//当前一个数字都还没有被使用过,因此u为0 a的值为0
dfs_a(0,0);
printf("%d\n",ans);
return 0;
}