问题描述
100 可以表示为带分数的形式:100 = 3 + 69258 / 714。
还可以表示为:100 = 82 + 3546 / 197。
注意特征:带分数中,数字1~9分别出现且只出现一次(不包含0)。
类似这样的带分数,100 有 11 种表示法。
输入格式
从标准输入读入一个正整数N (N<1000*1000)
输出格式
程序输出该数字用数码1~9不重复不遗漏地组成带分数表示的全部种数。
注意:不要求输出每个表示,只统计有多少表示法!
样例输入1
100
样例输出1
11
样例输入2
105
样例输出2
6
由于题目要求数字1-9分别出现一次且只出现一次,那么我们首先想到的就是全排列数字1-9,针对每一个全排列,遍历放置+和/的位置,如果遍历到使a+b/c==n 且 b%c==0的结果,就让答案个数+1.
对于蓝桥题目说是1~9不重复,给出一个条件,大概率用全排列函数可以解决的。
并且这里我们可以运用next_permutation()函数进行全排列。
使用该函数时需要头文件
使用方法:next_permutation(数组头地址,数组尾地址);若下一个排列存在,则返回真,如果不存在则返回假。
例如数组名为num,next_permutation(num,num+n)函数是对数组num中的前n个元素进行全排列,同时并改变num数组的值。
int a[10] = {1,2,3,4,5,6,7,8,9},sum=0;
while(next_permutation(a,a+9))
{
}
本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。
看这个算式:
☆☆☆ + ☆☆☆ = ☆☆☆
如果每个五角星代表 1 ~ 9 的不同的数字。
这个算式有多少种可能的正确填写方法?
#include
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<
本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。
A+B/C+DEF/GHI=10
这个算式中 A ~ I 代表 1 ~ 9 的数字,不同的字母代表不同的数字。
(式子改成:a+b/c+m/n=10,其中,a,b,c都是一位数,m,n都是三位数。总共10个数字,并且这些数是1~9.不能重复)
比如:
6+8/3+952/714 就是一种解法,
5+3/1+972/486 是另一种解法。
这个算式一共有多少种解法?
思路:全排列用起来,还有一点,这道题通分很关键。
至于为什么需要通分,这里我们可以对题目给出的实例进行分析:
例如6+8/3+952/714,若不通分,8/3和952/714都除不尽,这样除之后的小数相加,不能准确的等于10.
另外符合(bn+cm)/cn=10-a(a+b/c+m/n=10)表达式的a、b、c、m、n这五个数在进行(bn+cm)/cn运算操作的时候,结果肯定是个整数,因为10和a都为整数,那么相减必然为整数,而(bn+cm)/cn的结果等于10-a;但是我们不排除不满足(bn+cm)/cn=10-a该等式时(bn+cm)/cn是一个小数,且很有可能本身(bn+cm)/cn!=10-a,但是由于(bn+cm)/cn声明的类型是int类型,会进行小数下取整操作,取整后的整数值等于10-a的整数值。
所以,为保证答案不被下取整,我们需要将参与运算的数据声明为double类型,或者将除运算左右的结果强制转换成double类型。
#include
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<
#include
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<
另外若将数据类型声明为Int类型,我们可以添加一个判断条件(xx*a[2]+a[1]*xxx)%(xxx*a[2])==0,这样即可排除下取整后等于10-a的结果了。
#include
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<
相信经过上述实例的讲解,大家对next_permutation和全排列的使用场景有所体会,接下来我们来看带分数这道题的代码。
注意带分数题目的要求与凑算式的不同之处,凑算式已经明确规定了等式出现的五个数据分别是几位数。而带分数位数并没有明确确定,位数不一定。
#include
#include
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<
分析一下时间复杂度 ,while循环中嵌套了两个for循环,时间复杂度是O(n*);(家人们这个时间复杂度,我不太确定,求大佬指点);
即为两层for循环的时间复杂度,9个数,8个空隙中找两个位置放+和/运算符。
在介绍另一种思路之前,我们先来介绍一下递归求全排列。
思路比较简单,即依次枚举每个位置放哪个数,记住将递归都转换为递归搜索树,方便理解。
这里给出递归搜索树:
同时这里给出时间复杂度的证明,最终该递归求全排列的时间复杂度为O(n*n!);
根据图示递归搜索树,我们的递归求全排列代码如下:
#include
#include
#include
#include
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
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<
该算法的时间复杂度为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
#include
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<
带分数这道题利用前两种方案,都是比较好理解的,最后一个方案,应用了dfs的嵌套,枚举a后枚举c,最后直接计算出b;思路在解题过程,已经讲解的比较清楚,但在时间复杂度这一块把握的不是很好,这里总结一下明确的时间复杂度:next_permutation的时间复杂度为O(n),n代表传入的需要进行全排列的数字个数;dfs递归求全排列的时间复杂度为O(n*n!).