蓝桥杯青少年创意编程大赛题解:带分数

题目描述

100 100 100 可以表示为带分数的形式: 100 = 3 + 69258 714 100=3+\frac{69258}{714} 100=3+71469258
还可以表示为: 100 = 82 + 3546 197 100=82+\frac{3546}{197} 100=82+1973546

注意特征:带分数中,数字 1 ∼ 9 1∼9 19 分别出现且只出现一次(不包含 0 0 0)。

类似这样的带分数, 100 100 100 11 11 11 种表示法。

输入格式

一个正整数。

输出格式

输出输入数字用数码 1 ∼ 9 1∼9 19 不重复不遗漏地组成带分数表示的全部种数。

数据范围

1 ≤ N < 1 0 6 1≤N<10^6 1N<106

输入样例1

100

输出样例1

11

输入样例2

105

输出样例2

6

算法思想1(DFS)

根据题目描述,带分数满足 n = a + b c n = a + \frac{b}{c} n=a+cb。要计算 a , b , c a,b,c a,b,c的情况,可以将等式左右两边同时 × c \times c ×c得到 n c = a c + b nc = ac + b nc=ac+b。因此可以先枚举 a a a c c c,再计算 b b b的值,最后判断 a , b , c a,b,c a,b,c是否符合条件即可。
由于在带分数中数字 1 ∼ 9 1∼9 19 分别出现且只出现一次,可以使用暴力搜索的方式先确定 a a a,对于 a a a的每种符合条件的取值,继续搜索 c c c的情况。对于一组 a a a c c c的取值,计算出 b b b,再判断 b b b是否符合条件即可。

代码实现1

#include 
#include 
using namespace std;
const int N = 20;
int n, ans;
int st[N], backup[N];

bool check(int a, int c)
{
    //将带分数等式两边同时乘c,计算b
    long long b = (long long) n * c - a * c;
    //不能为0
    if(!a || !b || !c) return 0;
    //将st备份到backup,对b进行检查
    memcpy(backup, st, sizeof st);
    
    while(b)
    {
        int o = b % 10;
        b /= 10;
        //如果b的个位为0,或者已经使用过
        if(!o || backup[o]) return 0;
        backup[o] = 1;
    }
    
    //判断是否存在没有用到的数字
    for(int i = 1; i <= 9; i ++)
        if(!backup[i]) return 0;
    
    return 1;
}

//对a的每个取值,搜索c的值
void dfs_c(int t, int a, int c)
{
    //如果9个数字都已搜索过,结束搜索
    if(t > 9) return;
    
    //对一组a、c判断是否满足要求
    if(check(a, c)) ans ++;
    
    for(int i = 1; i <= 9; i ++)
    {
        if(!st[i])
        {
            st[i] = 1;
            //将当前数字作为c的个位,继续向下搜索
            dfs_c(t + 1, a, c * 10 + i);
            //恢复现场
            st[i] = 0;
        }
    }
}

//搜索a的取值,t表示已经确定的数字个数
void dfs_a(int t, int a)
{
    //a作为带分数的一项,结果不能超过n
    if(a >= n) return;
    
    //对于a的每个合法取值(不能为0),继续搜索c的情况
    if(a) dfs_c(t, a, 0);
    
    for(int i = 1; i <= 9; i ++)
    {
        //每个数字只能出现一次
        if(!st[i])
        {
            st[i] = 1;
            //将当前数字作为a的个位,继续向下搜索
            dfs_a(t + 1, a * 10 + i);
            //恢复现场
            st[i] = 0;
        }    
    }
}

int main()
{
    cin >> n;
    dfs_a(0, 0);
    cout << ans << endl;
    return 0;
}

算法思想2(枚举)

再提供一种时间复杂度更优的枚举思想。根据题目描述,在带分数中,数字 1 ∼ 9 1∼9 19 分别出现且只出现一次(不包含 0 0 0)。可以推断在带分数中,分母不会超过 4 4 4位数,且最大值为9876

若分母超过4位数,为了使分数的结果为整数,则分子的位数至少为5位,不满足题意。

因此可以枚举分母,以及每个分母的整数倍,在其中选择符合条件的解即可。其中分母的整数倍不会超过 6 6 6位数字,且最大值为987654

在枚举的过程中,注意要对不符合条件的解及时剪枝,以降低时间复杂度。

代码实现2

#include 
using namespace std;
//检查是否包含0,以及每个数的出现次数是否合法
bool check(int x)
{
    int cnt[10] = {0};
    while(x)
    {
        int o = x % 10;
        if(cnt[o] || o == 0) return false;
        cnt[o] ++;
        x /= 10;
    }
    return true;
}

//检查1~9是不是只出现1次
bool check(int a, int b, int c)
{
    int res = 0;
    int cnt[10] = {0};
    while(a)
    {
        int o = a % 10;
        if(cnt[o] || o == 0) return false;
        cnt[o] ++;
        res ++;
        a /= 10;
    }
    while(b)
    {
        int o = b % 10;
        if(cnt[o] || o == 0) return false;
        cnt[o] ++;
        res ++;
        b /= 10;
    }
    while(c)
    {
        int o = c % 10;
        if(cnt[o] || o == 0) return false;
        cnt[o] ++;
        res ++;
        c /= 10;
    }
    
    return res == 9;
}

int main()
{
    int x;
    cin >> x;
    
    int res = 0;
    //枚举分母
    for(int i = 1; i <= 9876; i ++)
    {
        if(!check(i)) continue;
        //枚举分母的倍数,注意倍数要小于x
        for(int j = 2; j < x && i * j <= 987654; j ++)
        {
            if(!check(i * j)) continue;
            
            int a = x - j;
            
            if(check(a, i, i * j))
            {
                res ++;
                //cout << a << " + " << i * j  << " / " << i << endl;
            }
        }
    }    
    cout << res << endl;
	return 0;
}

你可能感兴趣的:(蓝桥杯青少年创意编程)