带分数(y总字幕版本,思路贼清晰)
y总的核心思想是:我们如果用暴力解法的话,n是用不到的,只能用于最后的判断,所以这个时候我们考虑把两边同时乘上c,把乘法转换为除法,
然后等式就成了 nc=ca+b,那么我们就可以用上n了,(当然了这个不是重点),而且我们通过观察这个式子,我们本来需要枚举a和b和c,
但是我们现在只需要枚举一下a和c就可以了,因为这样子的话b可以通过 b=cn-ca 直接把b算出来,就不需要枚举b了,这样子的话可以优化很多
当然了,光有这个思路还是远远不够的,这才只是一个思路,但是你想把这个转换成代码还是很有难度的,特别是对于我这种初学者来说,所以
我们还需要分析一下如何写代码。所以我们现在枚举的方式就变成了先枚举a,然后对于每一个枚举的a来说,我们再去枚举c,那么对于每一个枚举
的c来说,a和c已经确定了,那么b也就确定了,就可以进行一个check的判断。虽然这里的话是做了一个很大的一个优化,但是代码写起来还是比较
复杂的。我们先写一个dfs_a函数,先dfs一下a,它其实是一个排列对吧,枚举一个排列,1,2,3,4…然后在dfs_a的内部,在每一个叶子节点的时
候,每一个叶子节点的地方,(因为每一个叶子就代表每一个a的可能成立的方案),那么在a的每一个叶子节点的地方,我们都需要再枚举一下c,
也就是我们要把a的每一个节点扩展成一个搜索树(即在a的每一个叶节点的时候dfs一下c),叶子上的每一棵树都是对c的搜索,因此我们这里其实
是dfs的一个嵌套关系,(即在一个dfs的过程中,把它的叶节点再dfs一下) 所以我们需要在dfs搜索a的时候在a的叶节点的基础上搜索一下对应的
c是多少,那搜完c之后的话我们再去判断一下b就可以了,也就是在c的每一个叶子节点上判断一下b就可以了。所以整个树的话会比较复杂一些。
开一个判重数组,首先dfs一下a,第一个参数表示我们当前已经用了多少个数字,第二个参数表示a是多少,然后直接输出答案
然后写一下dfs_a ,首先判断一下,如果 a>=n 了,那么就不满足条件了对吧,那就直接return了。如果a是可行的,那么我们就dfs一下c
对于dfs_a,然后我们来枚举一下当前这个位置能用哪几个数字,从1到9进行循环,如果说当前这个位置上的数没有被用过的话,那么我们就用它,
并做上标记,然后我们再dfs到下一层,同时更新一下a,传到下一层的参数应该是第一个参数+1,然后a应该变成 a*10+i 。然后最后要记得恢复现
场,因为这里存在一个回溯。
这个是dfs_a,那么我们现在实现一下 dfs_c ,dfs_c的话有三个参数,dfs_c的第一个参数和dfs_a的参数一样,表示我们当前已经用了多少个数,
第二参数的话是a的值,因为我们要利用a的值,第三个参数代表当前c的值。
如果说第一个参数已经等于n了,那就说明我们用完n个数字了对吧,那我们就可以直接return了。否则的话那我们就可以check一下,如果说check
成功的话,那我们的答案就++。(check函数的话就是用来判断一下a和c是否满足要求,传的第一个参数为a,第二个参数为b)否则的话我们就从
1到9枚举一下c,如果这个数没被用过的话,那我们就把它标记一下,并把它放在c的后面,再dfs下一层,下一层的参数就是第一个参数+1,然后
第二个参数不变,第三个参数就是 c*10+i ,然后我们还是要恢复现场。
然后最后再实现一下这个check函数,函数的第一个参数就是a,第二个参数就是c,我们来判断一下a和c是不是满足要求的。那判断a和c是否满足
要求我们需要先利用 b=cn-ca 把计算出来对吧,然后我们再对b的每一位数字分析,第一点看一下它有没有和a和c有相同的数字冲突,第二点
看一下a,b,c是否已经把9个数字全部用完了,那这里的话我们可以搞一个判重数组,我们先把原来的那个数组copy过来,因为我们要对这个判重
数字进行修改,但是我们原来的判重数组是需要保证其是原样的,因为它需要恢复现场(回溯),所以这就是我们多开一个判重数组的原因。然后
我们来取出b的每一位进行判断。在判断的过程中,如果b的某位是0或者b的某位已经出现过了,那就说明出错了,那就直接return false。否则的
话我们给它标记一下它已经用过了,然后我们最后再去遍历一下数组中从1到9的每个数字是否已经被用了(这里不需要判断是否存在多用的情况,
因为不可能存在,因为我们每次都会标记,只有没被用过的数字才会被使用,如果已经被用过的数字将不会被使用),如果判断过程中存在没有被
用过的数字,那说明不满足条件,直接return false。如果上述情况都满足,那就最后return true。当然了这个里面的话我们可以提前判断一些
边界,这个题目中a和b和c都不能为0对吧,所以我们可以在check的一开始判断一下,a或b或c中是否存在0,如果存在,直接return false,这样就
可以做到一个小小的优化以及边界的判定。
总的来说就是我们先枚举一下a,然后对于每个a枚举一下c,然后对于每个a和c我们去判断一下a,b,c是否满足条件就可以了
1、通过公式的转化,将枚举三个变量转换成枚举两个变量(因为第三个变量可以计算出来)
2、枚举 a
3、对于每个a,枚举c
4、对于每个枚举的a和c来说,我们来判断一下b是否成立就可以了
下面是AC代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N=520;
int had_use[N],ever[N];
int ans=0;
int n;
bool check(int a,int c)
{
int b=n*c-a*c;//把公式整理一下,然后先把b计算出来
if(!a||!b||!c)return false;
memcpy(ever,had_use,sizeof had_use);//因为我们要对这个判断是否出现的数组进行修改,但是原数组又不能变化,所以我们
//额外开一个数组进行使用,这样就可以达到判断且不会改变原数组的目的
while(b)
{
int t=b%10;//取它的每一位,用来更新一下用过的数字
b/=10;//删掉这个已经被选中的数
if(!t||ever[t])return false;
ever[t]=1;
}
for(int i=1;i<=9;i++)//遍历一下,判断每个数
if(!ever[i])
return false;
return true;
}
void dfs_c(int x,int a,int c)//x表示我们已经用了多少个数字
{
if(x>=10)return;//如果我们把10个数字都用了的话,那就直接return了
if(check(a,c))ans++;//如果满足要求,那我们判断一下a,c是否符合题目要求,如果符合,那么答案++
for(int i=1;i<=9;i++)//否则的话我们把c从1到9全部枚举一遍
if(!had_use[i])
{
had_use[i]=1;
dfs_c(x+1,a,c*10+i);//如果这个数没用过,那么我们就把它放在c的后面,继续dfs下一层
had_use[i]=0;
}
}
void dfs_a(int x,int a)
{
if(a>=n)return;
if(a)dfs_c(x,a,0);//如果说a是满足情况的,那么我们就枚举一下c,后面那个0表示c的大小
for(int i=1;i<=9;i++)//枚举一下当前这个位置可以用哪些数字
if(!had_use[i])
{
had_use[i]=1;
dfs_a(x+1,a*10+i); //如果这个数没有被用过,那么我们就加上它,并且dfs下一层
had_use[i]=0;//恢复现场,回溯一下
}
}
int main()
{
scanf("%d",&n);
dfs_a(0,0);//第一个0表示我们已经用了多少个数字,后面那个0表示我们当前的a是多少
printf("%d",ans); return 0;
}
这个题解是从acwing上面搬过来的,也是自己写的~~
觉得acwing上看的人挺多的就搬过来分享给大家了~~
好了,每篇博客后面都附上一句话:
我相信我的等待会有结果。