带分数

带分数

题目描述

在这里插入图片描述


题意解释

给定一个数 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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷心菜不卷Iris

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值