关于数位DP的学习
---恢复内容开始---
因为最近做比赛经常会出现数位DP,便尝试着去学学看数位DP。
先给出两篇论文的链接:
然后也是寻找了很多大牛的博客,学习了很多(但是没学会囧。),现在先总结一下已经学到的东西
“在信息学竞赛中,有这样一类问题:求给定区间中,满足给定条件的某个D 进制数或
此类数的数量。所求的限定条件往往与数位有关,例如数位之和、指定数码个数、数的大小
顺序分组等等。题目给定的区间往往很大,无法采用朴素的方法求解。此时,我们就需要利
用数位的性质,设计log(n)级别复杂度的算法。解决这类问题最基本的思想就是“逐位确定”
的方法。下面就让我们通过几道例题来具体了解一下这类问题及其思考方法。”——刘聪
事实上,为什么会想到用数位DP来做,就是因为限定条件往往和数位有关,而仔细地朴素的暴力方法中,所做的重复的工作太多。这样的条件会使得DP(记忆化搜索)有用武之地。
目前我所接触到的大多数的题,都是可以通过记录某些值(比如数位等)来减少重复的运算。当然,因为此类题的特殊性,可以编写check函数确定代码的正确性。
再偷用某个大牛的一句话:其实数位DP(或者说所有记忆化搜索)都是可以看做通过搜索来填满状态的值。
首先先想想数位DP的运行模式
如果我们要统计[0,54321]中满足某个条件的个数,需要将其拆分为
[00000,09999][10000,19999],[20000,29999],[30000,39999],[40000,49999],
[50000,50999],[51000,51999],[52000,52999],[53000,53999],
[54000,54099],[54100,54199],[54200,54299],
[54300,54309],[54310,54319],
[54320,54321]
为什么要这么分呢?随便举个例子,如果我们统计过了[0000,9999]中的满足条件(或者其他各种不满足条件的状态)的个数,那么分别在加上前缀,就可以判断出有多少个满足条件的个数。目的是为了将大的区间划分为小的区间进行求解。
因此,总结一句话,数位DP减少的运算量为:前面几位固定,后面几位可以任意取的个数统计。
比如分析一道简单题:HDU 3652,通过我这个渣渣的错误历程来分析一些细节上的问题
先贴错误代码
1 #include<cstdio>
2 #include<cstring>
3 #include<algorithm>
4 using namespace std;
5 int dp[20][20][20];
6 int num[20];
7 int dfs(int pos,int mod,int pre,int stat,int limit){
8 if(pos==0) return mod==0&&stat;
9 if(!limit&&dp[pos][mod][pre]!=-1) return dp[pos][mod][pre];
10 int ans=0;
11 int end=limit?num[pos]:9;
12 for(int i=0;i<=end;i++){
13 int nmod=(mod*10+i)%13;
14 int nstat=(pre==1&&i==3)||stat;
15 ans+=dfs(pos-1,nmod,i,nstat,limit&&i==end);
16 }
17 if(!limit) dp[pos][mod][pre]=ans;
18 return ans;
19 }
20 int cal(int x){
21 int cnt=0;
22 memset(num,0,sizeof(num));
23 while(x){
24 num[++cnt]=x%10;
25 x/=10;
26 }
27 return dfs(cnt,0,0,0,1);
28 }
29 int main()
30 {
31 int i,j;
32 int n;
33 memset(dp,-1,sizeof(dp));
34 while(scanf("%d",&n)!=EOF){
35 int ans=cal(n);
36 printf("%d\n",ans);
37 }
38 return 0;
39 }
我选取的记录参数有三个:pos当前处理位,mod前缀和余数,还有前一位的数字pre
但是运算结果却始终会小于等于正确的答案,为什么呢?
想了想,发现其实是因为参数含义的问题。
分析一下,如果我将pre作为一个关键参数记录下来,其实我并不能区分我记录的是后面几位能不能随机取的个数。
即当下一次搜索到pos,mod,pre的时候,不能确定前面是否有13,或者以前搜索的DP[pos][mod][pre]中的数的个数是否有13。
因此应该把记录的参数改为pos,mod,stat(表示为记录状态,0为不含13,1为只含前一位为1,2为前缀含有13)。
因此得到下面的AC代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int dp[20][20][3];
int num[20];
int dfs(int pos,int mod,int stat,int limit){
if(pos==0) return mod==0&&stat==2;
if(!limit&&dp[pos][mod][stat]!=-1) return dp[pos][mod][stat];
int ans=0;
int end=limit?num[pos]:9;
for(int i=0;i<=end;i++){
int nmod=(mod*10+i)%13;
int nstat;
if(stat==1&&i==3||stat==2) nstat=2;
else if(i==1) nstat=1;
else nstat=0;
ans+=dfs(pos-1,nmod,nstat,limit&&i==end);
}
if(!limit) dp[pos][mod][stat]=ans;
return ans;
}
int cal(int x){
int cnt=0;
memset(num,0,sizeof(num));
while(x){
num[++cnt]=x%10;
x/=10;
}
return dfs(cnt,0,0,1);
}
int main()
{
int i,j;
int n;
memset(dp,-1,sizeof(dp));
while(scanf("%d",&n)!=EOF){
int ans=cal(n);
printf("%d\n",ans);
}
return 0;
}
好了讲完了
关于数位DP的学习的更多相关文章
- 数位DP::SoSDP
数位DP:: SoSDP 学习博客(待补) 下面做一些例题: SPECIAL PAIRS 题意 给n个数字,求这些数字有多少对的\(AND\) 结果是0.数字不大于1e6.顺序反相反视为不同的对. 思 ...
- HDU3555【数位DP】
入门...还在学习中,先贴一发大牛博客 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3555 题目大意: 给一个数字n,范围在1~2^63-1,求1~ ...
- 掌握数位dp
最近遇到了数位dp题目,于是就屁颠屁颠的跑过来学习数位dp了~ "在信息学竞赛中,有这样一类问题:求给定区间中,满足给定条件的某个D 进制数或此类数的数量.所求的限定条件往往与数位有关,例如 ...
- 数位DP学习笔记
数位DP学习笔记 什么是数位DP? 数位DP比较经典的题目是在数字Li和Ri之间求有多少个满足X性质的数,显然对于所有的题目都可以这样得到一些暴力的分数 我们称之为朴素算法: for(int i=l_ ...
- MMM 数位dp学习记
数位dp学习记 by scmmm 开始日期 2019/7/17 前言 状压dp感觉很好理解(本质接近于爆搜但是又有广搜的感觉),综合了dp的高效性(至少比dfs,bfs优),又能解决普通dp难搞定的问 ...
- [学习笔记] 数位DP的dfs写法
跟着洛谷日报走,算法习题全都有! 嗯,没错,这次我也是看了洛谷日报的第84期才学会这种算法的,也感谢Mathison大佬,素不相识,却写了一长篇文章来帮助我学习这个算法. 算法思路: 感觉dfs版的数 ...
- 【学习笔记&训练记录】数位DP
数位DP,即对数位进行拆分,利用数位来转移的一种DP,一般采用记忆化搜索,或者是先预处理再进行转移 一个比较大略的思想就是可以对于给定的大数,进行按数位进行固定来转移记录答案 区间类型的,可以考虑前缀 ...
- ACM学习历程—HDU5587 Array(数学 && 二分 && 记忆化 || 数位DP)(BestCoder Round #64 (div.2) 1003)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5587 题目大意就是初始有一个1,然后每次操作都是先在序列后面添加一个0,然后把原序列添加到0后面,然后 ...
- 数位DP 学习笔记
前言:鸣谢https://www.luogu.com.cn/blog/virus2017/shuweidp.感谢大佬orz ----------------------------- [引入] 首先要 ...
随机推荐
- Command3
压缩和解压 gzip gunzip .gz 指定文件必须带后缀 gzip file filename.gz zip unzip .zip unzip filename.zip directory zi ...
- 镜像批量迁移利器:image-transfer
概述 用户业务在上云或者云迁移过程中,需要对镜像进行批量迁移.基于此背景,腾讯云容器专家团队开发了镜像批量迁移工具:image-transfer.该工具支持多种云厂商镜像仓库之间的批量迁移,同时支持腾 ...
- 【MySQL 基础】MySQ LeetCode
MySQL LeetCode 175. 组合两个表 题目描述 表1: Person +-------------+---------+ | 列名 | 类型 | +-------------+----- ...
- shell 脚本安装Tomcat和java
脚本安装Tomcat和java#!/bin/bash##SCRIPT:install_jdk-8u181-linux-x64_apache-tomcat-8.0.53#AUTHOR:Shinyinfo ...
- docker迁入迁出mysql
docker迁出mysql数据库 测试环境: docker服务器 mysql服务器 IP 192.168.163.19 192.168.163.16 操作系统 CentOS7.8 CentOS7.8 ...
- 从一次生产消费者的bug看看线程池如何增加线程
0 背景 某个闲来无事的下午,看到旧有的项目中,有个任务调度的地方都是同步的操作,就是流程A的完成依赖流程B,流程B的完成依赖流程C,按此类推. 作为一名垃圾代码生产者,QA的噩梦.故障报告枪手的我来 ...
- 【译】Async/Await(二)——Futures
原文标题:Async/Await 原文链接:https://os.phil-opp.com/async-await/#multitasking 公众号: Rust 碎碎念 翻译 by: Praying ...
- (十七)logging模块
logging模块是Python内置的标准模块,主要用于输出运行日志. 简单应用 import logging logging.debug('+++debug+++') logging.info('+ ...
- canvas多重阴影发光效果
canvas多重阴影发光效果 前言 在一个项目中,客户提了一个发光的效果,效果图如下: 阴影 有的人可能会说,这个用阴影其实就可以实现.但是从图中可以看出,是一个比较强烈的发光效果.实际的应用过程中我 ...
- 【Linux】find查找空文件夹
linux下批量删除空文件(大小等于0的文件)的方法 find . -name "*" -type f -size 0c | xargs -n 1 rm -f 就是删除1k大小的文 ...