当前位置: 首页 > news >正文

【蓝桥杯2024省B】好数 三种解法全解析 | C/C++暴力法→剪枝优化→构造法演进

好数题解:暴力遍历、剪枝优化与数位构造

点击查看题目

摘要

本文针对「好数」统计问题提出三种递进式解法:暴力遍历通过逐位验证实现简单逻辑;剪枝优化利用个位必为奇数的特性减少50%遍历量;数位构造法采用DFS直接生成合法数字,将时间复杂度优化至指数级。文章提出方法选型指南:小数据(≤1e4)适用暴力法,中等数据(1e4-1e7)推荐剪枝优化,大数据(≥1e7)首选构造法,并探讨了数位DP、并行计算等进阶优化方向,完整呈现算法优化方法论。

题目解析

题目定义 定义满足以下条件的正整数为"好数":

  • 从低位开始计数(个位为第1位)
  • 奇数数位(第1、3、5…位)必须为奇数
  • 偶数数位(第2、4、6…位)必须为偶数

输入输出

  • 输入:正整数N
  • 输出:1到N范围内的好数总数

示例解析

输入#1

24

输出#1

7

解释:符合条件的数字有1,3,5,7,9,21,23

输入#2

2024

输出#2

150


理解题意

根据题目定义,"好数"需满足如下数位特征规律:当数字的某位处于奇数位(即个位为第1位、百位为第3位、万位为第5位等)时,该位数字必须为奇数;当处于偶数位(十位为第2位、千位为第4位、十万位为第6位等)时,该位数字必须为偶数

思路

那么第一个方法是暴力遍历1到n的数字,看看他们符不符合,不过还可以优化,第二种方法是在第一种方法的基础上跳过一些不符合的数字,虽然上面两种方法可以过,但我们不能仅求于此,仍然可以优化,第三个方法就是直接构造符合题意的好数,跳过无关的数字。下面分享我的三种种解法

解法对比

方法时间复杂度优化策略适用场景
暴力遍历O(n·k)N≤1e4
剪枝优化O(n/2·k)跳过偶数遍历N≤1e7
数位构造法O(5^{log₁₀N})合法数字生成N>1e7

当N为最坏情况,N=1e7时:

  • 暴力法:遍历7×1e7次
  • 剪枝优化:遍历7×1e7/2次
  • 构造法:仅需构造5^8=390,625次

方法一:暴力遍历——基础解法

算法思路

  1. 全量遍历:逐个检查1到N的所有数字
  2. 逐位验证
    • 奇数位(个/百/万位)必须为奇数
    • 偶数位(十/千/十万位)必须为偶数

代码

#include<bits/stdc++.h>
using namespace std;
int n;
int ans=0;
bool check(int x)
{
    int tem = x;
    int f=0;//0奇1偶
    while(tem)
    {
        if(f==0)
        {
            if(tem%10 % 2 == 0)//奇数位为偶
            {
                return false;
            }
        }
        else 
        {
            if(tem%10 % 2 != 0)//偶数位为奇
            {
                return false;
            }
        }
        tem /= 10;
        if(f==0) f=1;
        else f=0;
    }
    return true;
}
int main() {
    cin>>n;
    //遍历判断
    for(int i=1;i<=n;++i)
    {
        if(check(i))
        {
            ans++;
        }
    }
    cout<<ans;
    return 0;
}

复杂度分析

  • 时间复杂度:O(n·k)(k为数字平均位数)
  • 空间复杂度:O(1)

方法二:剪枝优化——效率提升50%

优化洞察

个位必为奇数的特性意味着:

  • 所有偶数必然不符合条件
  • 遍历步长可优化为2(仅检查奇数)

代码实现

#include<bits/stdc++.h>
using namespace std;
int n;
int ans=0;
bool check(int x)
{
    int tem = x;
    int f=0;//0奇1偶
    while(tem)
    {
        if(f==0)
        {
            if(tem%10 % 2 == 0)//奇数位为偶
            {
                return false;
            }
        }
        else 
        {
            if(tem%10 % 2 != 0)//偶数位为奇
            {
                return false;
            }
        }
        tem /= 10;
        f = 1 - f;//取反操作
    }
    return true;
}
int main() {
    cin>>n;
    //遍历判断
    for(int i=1;i<=n;i+=2)
    {
        if(check(i))
        {
            ans++;
        }
    }
    cout<<ans;
    return 0;
}

性能提升

优化点效果
遍历次数减半时间复杂度降为O(n/2·k)
快速失败机制平均减少20%校验时间

方法三:数位构造法——终极优化

算法思想

采用DFS生成合法数字:

  1. 数位预定义
    • 奇数位候选:{1,3,5,7,9}
    • 偶数位候选:{0,2,4,6,8}
  2. 递归构造
    • 从高位到低位生成数字
    • 及时终止超限分支
#include<bits/stdc++.h>
using namespace std;
int n;
int ans=0;//答案
int flag=0;//标记当前构造的数字是否超过n,由于我们构造的数字的顺序是严格递增的,所以超过的时候直接输出答案退出
bool hx[10000005]={0};//判断构造的重复
int fac[10]={0};//存储构造的数字,fac[1]、fac[2]...是构造的数字的个位、十位...
int ji[6]={0,1,3,5,7,9};//奇数位可能的值
int ou[6]={0,0,2,4,6,8};//偶数位可能的值
int is_int(int len)//数组转int函数,len是构造的数字fac数组的长度
{
    int tem = 0;
    int f=1;
    for(int i=1;i<=len;++i)
    {
        tem += f*fac[i];
        f *= 10;
    } 
    return tem;
}
void dfs(int len, int index)//len是总位数,index是当前构造的是第几位,从数字的高位到低位
{
    //flag为1,即已经构造了大于n的数了,那么后面再构造也是大于n,不符合条件,直接退出
    if(flag==1) return;
    //构造完了最低位,可以进行判断
    if(index == 0)//判断
    {
        int gz = is_int(len);
        //构造的数字大于n,后面也不用遍历了
        if(gz > n)
        {
            flag=1;
            cout<<ans;
            return ;
        }
        else if(hx[gz]==0)//判断是否之前就已经构造过
        {
            hx[gz] = 1;
            ans++;
            return ;
        }
        return ;
    }
    //枚举当前位数的可能值
    for(int i=1;i<=5;++i)
    {
        //存值
        if((index)%2 == 1)
        {
            fac[index] = ji[i];
        }
        else{
            fac[index] = ou[i];
        }
        //递归下一位
        dfs(len, index-1);
    }
}
int main()
{
    cin>>n;
    //题目的n不超过1e7,最大情况是8位数,所以探讨1-8位数的情况即可
    for(int i=1;i<=8;i++){
        if(flag == 1) break;
        dfs(i,i);
    }
    return 0;
}

核心机制

机制作用代码体现
动态候选集选择根据位数奇偶自动切换候选数字ji[ ],ou[ ]数组
预判剪枝发现超限立即终止后续构造flag标志
哈希去重避免重复计数hx[ ]数字

算法性能对比

测试环境:洛谷

输入规模暴力法耗时剪枝优化耗时构造法耗时
全部测试点284ms183ms55ms
  • 第一种
    在这里插入图片描述
  • 第二种
    在这里插入图片描述
  • 第三种
    在这里插入图片描述

思维拓展

进阶优化方向

  1. 数位DP优化
    结合动态规划记录中间状态,将时间复杂度优化至O(logN)

  2. 并行计算
    对暴力法进行多线程改造(OpenMP加速):

    #pragma omp parallel for reduction(+:ans)
    for(int i=1;i<=n;i+=2)
    
  3. 数学公式推导
    建立数位长度与可行数的直接关系式,实现O(1)时间复杂度


方法选型指南

场景推荐方法理由
N≤1e4暴力法实现简单,代码量少
1e4<N≤1e7剪枝优化平衡实现难度与性能
N>1e7数位构造法时间复杂度最优
多次查询/预处理构造法+预处理构造结果可缓存复用

竞赛技巧:根据数据规模选择策略,1e7是暴力与构造法的性能分水岭(1s限时最多2*1e7)


总结

三种方法体现了算法优化的典型路径:

  1. 暴力解法:建立问题认知的基础
  2. 剪枝优化:利用问题特性减少无效计算
  3. 构造解法:从根本上重构解题思路

通过这组解法,我们不仅解决了具体问题,更展示了算法优化中"观察特性→减少计算→重构思路"的经典方法论。

相关文章:

  • 特权FPGA之数码管
  • 一文详解OpenGL环境搭建:Ubuntu20.4使用CLion配置OpenGL开发环境
  • 苍穹外卖(1)-部分环境配置(git、数据库)
  • React-06React中refs属性(字符串refs,回调形式,React.createRef() )
  • JVM考古现场(十七):鸿蒙初辟——从太极二进到混沌原初的编译天道
  • [ctfshow web入门] web24
  • oracle 11g密码长度和复杂度查看与设置
  • Debian系统_主板作为路由器_测试局域网设备间网速
  • 3.Spring-AOP简介/AOP切入点表达式/AOP工作流程(p31-p35)
  • NO.78十六届蓝桥杯备战|数据结构-并查集|双亲表示法|初始化|查询|合并|判断|亲戚|Lake Counting|程序自动分析(C++)
  • [CI]Docker构建时传递CI的唯一生成号作为版本号(SpringBoot、Vue)
  • JVM性能调优:参数配置×内存诊断×GC调优实战
  • vue3中pinia基本使用
  • 前端核心知识:Vue 3 编程的 10 个实用技巧
  • 小程序设计和开发:怎样制定小程序的主要业务流程和功能模块。
  • 优化IntelliJ IDEA 对 Python 的提示功能
  • git配置 gitcode -- windows 系统
  • 云原生周刊:深入探索 kube-scheduler-simulator
  • 实时比分更新系统的搭建
  • zabbix中通过模板实现自动发现对tcp端口批量监控
  • 网站推广策划评估工具7/郑州seo建站
  • 自己做一个网站一年的费用/上海品牌推广公司
  • 网站咨询弹窗怎么做/杭州搜索引擎排名
  • 网站开发委托服务协议书/链接购买
  • 刷网站排名 优帮云/天津海外seo
  • 东莞网站优化哪个公司好/十大网络营销经典案例