蓝桥杯每日一题-----数位dp

news2024/11/15 22:22:08

前言

今天浅谈一下数位dp的板子,我最初接触到数位dp的时候,感觉数位dp老难了,一直不敢写,最近重新看了一些数位dp,发现没有想象中那么难,把板子搞会了,变通也会变的灵活的多!

引入

以一道例题作为数位dp的引入,题目如下,链接
在这里插入图片描述
数据范围为1e9,一般的算法很难把这道题拿下,类似求在一段区间范围内,满足某些条件的数字的个数,并且数据范围很大时就会联想到数位dp算法。

第一个板子

我遇到的数位dp板子有三个,第一个来源于y总的讲解,附上链接和这道题的代码,y总的讲解逻辑清晰,想学习可以直接看视频。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.Arrays;
import java.util.Scanner;

public class Main {
    static int[][] f = new int[11][10];
    static int k;
public static void main(String[] args) throws IOException {
    StreamTokenizer sc=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    sc.nextToken();
    //Scanner scanner = new Scanner(System.in);
    int t = (int)sc.nval;
    while(t> 0) {
        t--;
        sc.nextToken();
        k = (int)sc.nval;
        sc.nextToken();
        int l = (int)sc.nval;
        sc.nextToken();
        int r = (int)sc.nval;
        //int l = scanner.nextInt();
        //int r = scanner.nextInt();
        for (int i = 0; i < 11; i++) {
            Arrays.fill(f[i], 0);
        }
        init();
        System.out.println(dp(r) - dp(l - 1));
        //dp(r);
    }
    return;
}

private static int dp(int num) {
    // TODO Auto-generated method stub
    if(num == 0) {
        return 0;
    }
    int res = 0;
    int last = 0;//上一个位数的数字
    int[] nu = new int[12];
    int n = 1;
    while (num > 0 ) {
        nu[n++] = num%10;
        num = num / 10;
    }
    n--;
    //System.out.println(n);
    for (int i = n; i > 0; i--) {//遍历位数
        int x = nu[i];
        int jj;
        if(i == n) {
            jj = 1;
        }else {
            jj = 0;
        }
        //System.out.println(x);
        //System.out.println(last);
        for (; jj < x; jj++) {//遍历该位数上可以填的数字
        
            if(Math.abs(jj - last) <= k || i == n) {
                //System.out.println("mm" + i);
                res += f[i][jj];
            }
        }
        if(Math.abs(x-last) <= k || i == n) {
            last = x;
            //System.out.println("1111");
        }else {
            break;
        }
        if(i==1) {
            res++;
        }
    }
    //加包含前导0的,其实就是加上不是和num同位数的数字,
    for (int i = 1; i < n; i++) {
        for (int j = 1; j < 10; j++) {//从1开始
            res += f[i][j];
        }
    }
    //System.out.println(res);
    return res;
}

private static void init() {
    // TODO Auto-generated method stub
    for (int i = 0; i < 10; i++) {//初始化只有一位数字的时候,一定符合要求
         f[1][i] = 1;//注意i一定从0开始
    }
    for (int i = 2; i < 10; i++) {//初始化其它位数的数字
        for (int j = 0; j < 10; j++) {//注意,这里可以包含0
            for (int m = 0; m < 10; m++) {
                if(Math.abs(m-j) <= k) {
                    f[i][j] += f[i-1][m];
                }
            }
        }
    }
}
}

第二个板子

  1. 求区间[L,R]内符合条件的数字,转换为求区间[0,L-1]与[0,R]内符合条件的数字个数,答案就是这两个做差。
  2. 在遍历数字的时候,先遍历数字的位数,假设我要求[0,R]内满足条件的数字,R的位数为cnt,在遍历cnt的时候我要注意,不能超过R,如果R是23456,那么第cnt位能够选择的数字范围是[0,2]。如果我第cnt位选择了1,在遍历cnt-1位的时候我就不需要考虑遍历数字是否越界问题,因为cnt位已经决定了后面的位数怎么选都不会比23456大。如果第cnt位选择了2,在遍历cnt-1位时我可以填的范围就变成了[0,3],所以我需要有一个变量记录当前我前面选择的数字是否是贴上界的,令这个变量为limit,如果limit=1,当前位数可以选择的数字上界是a[cnt],否则就是9,其中数组a是把23456拆分的结果,拆分代码如下
int cnt = 0;
	while(n > 0) {
		a[++cnt] = (int) (n%10);
		n/=10;
	}

根据上述分析,会有如下代码,

int up = limit==1?a[cnt]:9;//求当前位数最大能够填的数字
limit&(i==up?1:0)//下一个limit是否还等于1,i表示当前位数填的数字,如果填了最大的那个数,且当前的limit是1,则填下一位数时能够填的最大数字也是受约束的

up表示当前位数可以填的数字上界。

  1. 接下来处理一下前导零,如果前导零的存在不会影响结果,那么不需要处理,否则就要处理一下前导零。比如求包含2和4的数字个数就不需要处理前导0,因为他不影响结果。如果要求数字各个位数不为0
    假设我要求[0,R]内满足条件的数字,R的位数为cnt,在遍历cnt的时候我要注意,在这个位置填的0就是前导0,如果我在这个位置填了0,再去遍历第cnt-1位数字时,这里填的0还是前导0.如果我第cnt位没有填0,那么我在cnt-1位填的0就不是前导0,他是有效的一维数,就像001和101一样,00里面的0都是前导0,是无效的。而10里的0是十位上的0,是有效的。我们用zeros来表示当前位的0是否是前导0,第cnt位的0肯定是前导0,如果第cnt位填了0,第cnt-1位的0才是前导0,否则就不是。所以有

    zeros&(i==0?1:0)//表示下一位的zeros是否是0,i表示当前位填的数字,如果当前位填了0并且当前位的zeros是1,那么下一位的zeros也是1.

    前导0的使用要比上界limit的使用更灵活一点,他是根据题目的要求来使用的。

  2. 这里主要讲记忆化递归。因为这一个板子用的是dfs,而dfs可能会有超时的问题,所以就有了记忆化递归。记忆化递归要标记好当前的状态,所以用来记忆当前状态的数组也是要根据题目设定。
    当题目中有多个测评样例时,进行记忆化的时候要注意不要记住在特定数下的答案。所以有下面的代码

    if(limit == 0) dp[cnt][last][zeros] = res;

    为什么要在limit=0时才进行记忆化呢?因为limit=1说明当前的数是贴上界的,而这个数是受当前这个样例影响的,比如2345这个数字,在遍历到百位数字3时,我是贴上界的,如果千位数字是2,那么此时十位数字只能选0-4,此时得到的res是从0-4递归得到的。但是如果换一个数字2377,在遍历到百位数字3时,我如果是不贴上界的,可选的数字应该是0-9,如果是贴上界的,可选的数字是0-7,明显此时的状态dp[3][3][0]和数字2345的转移是不一样的。所以我只有不贴上界的时候,说明后面的转移都是0-9时才进行记忆。
    读取记忆的时候也是同样的道理,

    if(limit==0&&dp[cnt][last][zeros]!=-1) { return dp[cnt][last][zeros]; }

    只有此时不贴上界的时候才能读取之前的记忆。
    记忆化数组根据具体的题目来设定,并不是统一的,具有高度灵活性,只要解释起来没问题就可以。像小明数这道题没有记忆limit,有些情况也是可以记忆limit的。

分析题目

针对小明数而言,前导0会影响答案,为什么?题目要求相邻两数之差绝对值不超过k,如果存在前导0并且不加以判断那么会认为前导0也是有效数字,那么0后面只能填0-k,但实际前导0应该是无效数字,前面一个数字是前导0,后面可以填0~9中的任意一个数字。如果没有判断前导零,会导致结果比实际的小。 求某些数字相邻位数上的数字之差的绝对值不超过k,来想一下dfs的时候需要什么,必然要有的是当前的位数cnt和是否贴上界limit,刚刚也分析了需要判断前导零,所以有zeros。遍历到cnt位时,来判断一下当前位可以填啥,我要满足相邻位数上的数字之差的绝对值不超过k,我得知道前一个位数我填的是几,所以就有了last,指示前一个位数上填的数字。然后就没有其它的了,所以 dfs(cnt,last,zeros,limit),接下来就直接放代码了。

import java.util.Arrays;
import java.util.Scanner;
public class Main {
	static int dp[][][] = new int[20][20][2];//还要记录当前的状态是不是有前导零!!!!!!!
	static int a[] = new int[20];
	static int k,ans;
	static int nums[] = new int[20];
public static void main(String[] args) {
	Scanner scanner = new Scanner(System.in);
   	int t = scanner.nextInt();
	while(t-- > 0) {
		for(int i = 0;i < 20;i++)
			for(int j = 0;j < 20;j++)
			Arrays.fill(dp[i][j],-1);
		k = scanner.nextInt();
		long l = scanner.nextLong();
		long r = scanner.nextLong();
		System.out.println(solve(r)-solve(l-1));

private static int solve(long n) {
	// TODO Auto-generated method stub
	int cnt = 0;
	while(n > 0) {
		a[++cnt] = (int) (n%10);
		n/=10;
	}
	return dfs(cnt,1,-1,1);
}

private static int dfs(int cnt, int limit, int last,int zeros) {//前导0对答案有影响!!!!!!
	// TODO Auto-generated method stub
	if(cnt==0) {
		return 1;
	}
	if(limit==0&&dp[cnt][last][zeros]!=-1) {
		return dp[cnt][last][zeros];
	}
	int res =0;
	int up = limit==1?a[cnt]:9;
	for(int i = 0;i <= up;i++) {
		if(Math.abs(last-i)<=k||zeros==1) {//3 1 2 0 dp[1][0]
			nums[cnt] = i;
			res += dfs(cnt-1, limit&(i==up?1:0), i,zeros&(i==0?1:0));//120
		}
	}
	if(limit == 0) dp[cnt][last][zeros] = res;	     
	return res;
}
}

如果代码有问题,欢迎在评论区内提出来!第三个板子就不讲啦,其实就是把第二个板子的dfs变成dp,但是个人感觉没有dfs灵活,目前在用第二个板子。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1431174.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

png图片怎么转换成jpg?四个方法搞定不求人

在数字图像处理领域&#xff0c;PNG和JPG是两种常见的图片格式。PNG以无损压缩而闻名&#xff0c;适用于保存透明背景和保留图像细节&#xff1b;而JPG以有损压缩而著称&#xff0c;适用于在较小的文件大小下保持照片质量。有时候&#xff0c;您可能需要将PNG格式的图片转换为J…

QXlsx Qt操作excel

QXlsx 是一个用于处理Excel文件的开源C库。它允许你在你的C应用程序中读取和写入Microsoft Excel文件&#xff08;.xlsx格式&#xff09;。该库支持多种操作&#xff0c;包括创建新的工作簿、读取和写入单元格数据、格式化单元格、以及其他与Excel文件相关的功能。 支持跨平台…

【git指南】git 远程代码控制

文章目录 git 远程代码控制1 创建远程仓库2 创建SSH通道3 上传文件到github4 从 github 下载项目5 对链接github的本地项目pull/push git 远程代码控制 ​ 上面介绍了 git 在本地的使用&#xff0c;下面介绍结合 github 的 git 的远程控制。 1 创建远程仓库 打开 github.com …

【lesson9】高并发内存池Page Cache层释放内存的实现

文章目录 Page Cache层释放内存的流程Page Cache层释放内存的实现 Page Cache层释放内存的流程 如果central cache释放回一个span&#xff0c;则依次寻找span的前后page id的没有在使用的空闲span&#xff0c;看是否可以合并&#xff0c;如果合并继续向前寻找。这样就可以将切…

备战蓝桥杯---搜索(应用基础1)

话不多说&#xff0c;直接看题&#xff1a; 显然&#xff0c;我们直接用深搜&#xff0c;我们可以先把空位用结构体存&#xff0c;然后打表存小方块&#xff0c;再用数组存行列。 下面是AC代码&#xff1a; #include<bits/stdc.h> using namespace std; int a[12][12];…

【Springcloud篇】学习笔记六(十一、十二章):Config分布式配置中心、Bus消息总线

第十一章_Config分布式配置中心 1.Config分布式配置中心介绍 1.1分布式系统面临的配置问题 微服务意味着要将单体应用中的业务拆分成一个个子服务&#xff0c;每个服务的粒度相对较小&#xff0c;因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行&…

图论练习2

内容&#xff1a;路径计数DP&#xff0c;差分约束 最短路计数 题目大意 给一个个点条边的无向无权图&#xff0c;问从出发到其他每个点的最短路有多少条有自环和重边&#xff0c;对答案 解题思路 设边权为1&#xff0c;跑最短路 表示的路径数自环和重边不影…

Blender教程(基础)-面的切割-10

快捷键K切割&#xff0c;菜单选项切割. 一、随意切割 物体在编辑模式下&#xff0c;按键盘K建切割物体。 二、中点切割 先按K键&#xff0c;再按shift键&#xff0c;会自动吸附到每条边的中点进行切割。 三、取消吸附 切割时会自动吸附到顶点或边 关闭快速吸附 按K键再按…

山东淄博刑侦大队利用无人机抓获盗窃团伙

山东淄博刑侦大队利用无人机抓获盗窃团伙 近期&#xff0c;山东淄博临淄区发生多起盗窃案件。通过视频追踪和调查访问&#xff0c;推断临淄区某村可能为嫌疑人藏匿地点。刑侦大队无人机应急小组迅速到达现场&#xff0c;经无人机高空侦查&#xff0c;发现并锁定了嫌疑人的藏匿…

PHP入门指南:API

PHP入门指南&#xff1a;API 1. 简介2. API的基础概念2.1 什么是API&#xff1f;2.2 API的类型2.3 API的作用2.4 RESTful API2.5 API的基本构成元素 3. PHP与API的交互基础3.1 发送HTTP请求3.2 处理HTTP响应3.3 异常处理3.4 确保安全性 4. 如何在PHP中创建一个简单的API4.1 设计…

【数据分享】1929-2023年全球站点的逐月降雪深度数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、能见度等指标&#xff0c;说到气象数据&#xff0c;最详细的气象数据是具体到气象监测站点的数据&#xff01; 之前我们分享过1929-2023年全球气象站点的逐月平均气温数据、逐月最高气温数据…

笔记---容斥原理

AcWing,890.能被整除的数 给定一个整数 n n n 和 m m m 个不同的质数 p 1 , p 2 , … , p m p_{1},p_{2},…,p_{m} p1​,p2​,…,pm​。 请你求出 1 ∼ n 1∼n 1∼n 中能被 p 1 , p 2 , … , p m p_{1},p_{2},…,p_{m} p1​,p2​,…,pm​ 中的至少一个数整除的整数有多少…

起心动念 | 生成式 AI 开发实践系列的开端

生成式 AI 和大模型的技术变革力量&#xff0c;正在逐渐影响着我们当下这个时代&#xff0c;全球各行各业都呈现百舸争流、万象更新的趋势。在 2023 年&#xff0c;我们通过各种面向开发者的活动&#xff0c;收集到了大量开发者关于基于生成式 AI 的开发内容需求和建议反馈。开…

免费CRM管理系统哪家好?如何选择?

免费的CRM系统有很多&#xff0c;以下是一些比较常见的&#xff1a;一、Zoho CRM&#xff1b;二、HubSpot CRM&#xff1b;三、Bitrix24&#xff1b;四、SuiteCRM&#xff1b;五、Agile CRM&#xff1b;六、Capsule CRM&#xff1b;七、Insightly。免费的CRM系统虽然功能相对较…

爬虫-网络空间微博信息管理系统的设计与实现-计算机毕业设计源码85633

摘 要 本论文主要论述了如何使用django框架开发一个网络空间微博管理信息系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述该系统的当前背景以及系统开发的目的&#xff0c;后续章节…

(每日持续更新)jdk api之ObjectInput基础、应用、实战

博主18年的互联网软件开发经验&#xff0c;从一名程序员小白逐步成为了一名架构师&#xff0c;我想通过平台将经验分享给大家&#xff0c;因此博主每天会在各个大牛网站点赞量超高的博客等寻找该技术栈的资料结合自己的经验&#xff0c;晚上进行用心精简、整理、总结、定稿&…

11. Threejs案例-绘制网格状圆环扭结

11. Threejs案例-绘制网格状圆环扭结 实现效果 知识点 TorusKnotGeometry (圆环缓冲扭结几何体) 创建一个圆环扭结&#xff0c;其特殊形状由一对互质的整数&#xff0c;p 和 q 所定义。如果 p 和 q 不互质&#xff0c;创建出来的几何体将是一个环面链接。 构造器 TorusKno…

解锁MyBatis Plus的强大功能:学习高级操作与DML技巧!

MyBatisPlus 1&#xff0c;DML编程控制1.1 id生成策略控制知识点1&#xff1a;TableId1.1.1 环境构建1.1.2 代码演示AUTO策略步骤1:设置生成策略为AUTO步骤3:运行新增方法 INPUT策略步骤1:设置生成策略为INPUT步骤2:添加数据手动设置ID步骤3:运行新增方法 ASSIGN_ID策略步骤1:设…

12种算法优化CNN-BiLSTM-Attention多特征输入单步预测,机器学习预测全家桶,持续更新,MATLAB代码...

截止到本期&#xff0c;一共发了12篇关于机器学习预测全家桶MATLAB代码的文章。参考文章如下&#xff1a; 1.五花八门的机器学习预测&#xff1f;一篇搞定不行吗&#xff1f; 2.机器学习预测全家桶&#xff0c;多步预测之BiGRU、BiLSTM、GRU、LSTM&#xff0c;LSSVM、TCN、CNN&…

leetcode正则表达式匹配问题(困难)

1.题目描述 2.解题思路&#xff0c;这道题自己没做出来&#xff0c;看了官方的题解&#xff0c;感觉对自己来说确实是比较难想的。使用了动态规划的解决方案&#xff0c;这种方案看题解都不一定能看明白&#xff0c;不过有个评论画图讲解的非常明白。其实仔细看题解的话&#…