模板 - 动态规划 - 数位dp
#include<bits/stdc++.h>
using namespace std;
#define ll long long int a[];
ll dp[][/*可能需要的状态1*/][/*可能需要的状态2*/];//不同题目状态不同
ll dfs(int pos,int state1/*可能需要的状态1*/,int state2/*可能需要的状态2*/,bool lead/*这一位的前面是否为零*/,bool limit/*这一位是否取值被限制(也就是上一位没有解除限制)*/)
//不是每个题都要处理前导零
{
//递归边界,最低位是0,那么pos==-1说明这个数枚举完了
if(pos==-)
return ;/*这里返回1,表示枚举的这个数是合法的,那么这里就需要在枚举时必须每一位都要满足题目条件,也就是说当前枚举到pos位,一定要保证前面已经枚举的数位是合法的。 */
//第二个就是记忆化(在此前可能不同题目还能有一些剪枝)
if(!limit && !lead && dp[pos][state1][state2]!=-)
return dp[pos][state1][state2];
/*常规写法都是在没有限制的条件记忆化,这里与下面记录状态对应*/
int up=limit?a[pos]:;//根据limit判断枚举的上界up
ll ans=;
//开始计数
for(int i=;i<=up;i++)//枚举,然后把不同情况的个数加到ans就可以了
{
int new_state1=???;
int new_state2=???;
/*
计数的时候用continue跳过不合法的状态,不再搜索
*/ //合法的状态向下搜索
ans+=dfs(pos-,new_state1,new_state2,lead && i==,limit && i==a[pos]);//最后两个变量传参都是这样写的
}
//计算完,记录状态
if(!limit && !lead)
dp[pos][state1][state2]=ans;
/*这里对应上面的记忆化,在一定条件下时记录,保证一致性,当然如果约束条件不需要考虑lead,这里就是lead就完全不用考虑了*/
return ans;
} ll solve(ll x)
{
//可能需要特殊处理0或者-1
if(x<=)
return ???; int pos=;
while(x)//把数位分解
{
a[pos++]=x%;//编号为[0,pos),注意数位边界
x/=;
} return dfs(pos-/*从最高位开始枚举*/,/*可能需要的状态1*/,/*可能需要的状态2*/,true,true);//刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛
} int main()
{
memset(dp,-,sizeof(dp));
//一定要初始化为-1 ll le,ri;
while(~scanf("%lld%lld",&le,&ri))
{
printf("%lld\n",solve(ri)-solve(le-));
}
}
其实另一种计数写法对别的题目有一定的启发性,需要特别注意的是,无论哪种写法的dp结果中存的数字都是和le与ri无关的。所以在数位受限时不能取用计算过的dp值,也不能更新dp值,不受限的情况可以重复利用。
无注释版:
#include<bits/stdc++.h>
using namespace std;
#define ll long long int a[];
ll dp[][MAXS1][MAXS2];
ll dfs(int pos,int s1,int s2,bool lead,bool limit) {
if(pos==-) {
return ?;
}
if(!limit && !lead && dp[pos][s1][s2]!=-)
return dp[pos][s1][s2];
int up=limit?a[pos]:;
ll ans=;
for(int i=; i<=up; i++) {
int ns1=op1(s1);
int ns2=op2(s2);
ans+=dfs(pos-,ns1,ns2,lead && i==,limit && i==a[pos]);
}
if(!limit && !lead)
dp[pos][s1][s2]=ans;
return ans;
} ll solve(ll x) {
if(x<=)
return ?; int pos=;
while(x) {
a[pos++]=x%;
x/=;
} return dfs(pos-,INITS1,INITS2,true,true);
} int main() {
memset(dp,-,sizeof(dp)); ll le,ri;
while(~scanf("%lld%lld",&le,&ri)) {
printf("%lld\n",solve(ri)-solve(le-));
}
}
一个更简单的模板,去掉了很多奇奇怪怪的东西,比如前导0,前导0的确应该特殊考虑而不能一概而论。
int dfs(int i, int s, bool e) {
if (i==-) return s==target_s;
if (!e && ~f[i][s]) return f[i][s];
int res = ;
int u = e?num[i]:;
for (int d = first?:; d <= u; ++d)
res += dfs(i-, new_s(s, d), e&&d==u);
return e?res:f[i][s]=res;
}
看起来清爽多了,其中:
f为记忆化数组;
i为当前处理串的第i位(权重表示法,也即后面剩下i+1位待填数);
s为之前数字的状态(如果要求后面的数满足什么状态,也可以再记一个目标状态t之类,for的时候枚举下t);
e表示之前的数是否是上界的前缀(即后面的数能否任意填)。
for循环枚举数字时,要注意是否能枚举0,以及0对于状态的影响,有的题目前导0和中间的0是等价的,但有的不是,对于后者可以在dfs时再加一个状态变量z,表示前面是否全部是前导0,也可以看是否是首位,然后外面统计时候枚举一下位数。
注意:
不满足区间减法性质的话,不能用solve(r)-solve(l-1)。
看了学长的部分博客之后发现其实使用f[i][j][st]表示以j开头的i位数满足条件st的数的个数也是可以的。待更新。
模板 - 动态规划 - 数位dp的更多相关文章
- 动态规划——数位dp
通过先前在<动态规划——背包问题>中关于动态规划的初探,我们其实可以看到,动态规划其实不是像凸包.扩展欧几里得等是具体的算法,而是一种在解决问题中决策的思想.在不同的题目中,我们都需要根据 ...
- 模板:数位DP
第一次听说dp还有模板的... 当然你要是记忆化搜索的话,就可以有一些套路 这是一个伪代码: LL Dfs(LL now,限制,LL top){ if(!now) return 判断条件; if(!t ...
- 动态规划-数位dp
大佬讲的清楚 [https://blog.csdn.net/wust_zzwh/article/details/52100392] 例子 不要62或4 l到r有多少个数不含62或者4 代码 #incl ...
- 模板 - 动态规划 - 区间dp
因为昨天在Codeforces上设计的区间dp错了(错过了上紫的机会),觉得很难受.看看学长好像也有学,就不用看别的神犇的了. 区间dp处理环的时候可以把序列延长一倍. 下面是 $O(n^3)$ 的朴 ...
- [转]数位dp小记
转载自:http://blog.csdn.net/guognib/article/details/25472879 参考: http://www.cnblogs.com/jffifa/archive/ ...
- HDU 5179 beautiful number 数位dp
题目链接: hdu: http://acm.hdu.edu.cn/showproblem.php?pid=5179 bc(中文): http://bestcoder.hdu.edu.cn/contes ...
- Pair(二进制处理+数位dp)(2019牛客暑期多校训练营(第七场))
示例: 输入: 33 4 24 5 27 8 5 输出:5 7 31 题意:存在多少对<x,y>满足x&y>C或x^y<C的条件.(0<x<=A,0< ...
- 数位dp模板 [dp][数位dp]
现在才想到要学数位dp,我是不是很弱 答案是肯定的 以一道自己瞎掰的题为模板 //题: //输入数字n //从0枚举到n,计算这n+1个数中含有两位数a的数的个数 //如12930含有两位数93 #i ...
- [模板] 数位dp
数位dp 简介 数位dp指满足特定性质的数的计数, 如求 \([l, r]\) 区间内不含 \(2\) 的数的个数. 一般来说, 数位dp利用dfs解决, 有时状态数较多, 需要hash表优化. 模板 ...
随机推荐
- 24、Cocos2dx 3.0游戏开发找小三之网格动作:高炫酷的3D动作
重开发人员的劳动成果,转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/37596763 网格动作类似于动作特效,能够实现翻转. ...
- Java for LeetCode 095 Unique Binary Search Trees II
Given n, generate all structurally unique BST's (binary search trees) that store values 1...n. For e ...
- 爬虫之重要的requests模块
一 . requests模块 什么是requests模块 requests模块是python中原生的基于网络请求的模块,其主要作用是用来模拟浏览器发起请求.功能强大,用法简洁高效.在爬虫领域中占据着半 ...
- EASYARM-IMX283 烧写uboot和linux系统
新入手一台EASYARM-IMX283开发板(以下简称IMX823),价格比较便宜,配置也不错. 开发板默认安装了WINCE,我还是决定重新烧写uboot和linux内核. 开发板配套光盘里面有不少烧 ...
- Shell 特殊位置参数的变量
$0 -- 获取当前执行shell脚本的 文件名 $n -- 获取当前执行shell脚本的 第n个参数 $# -- 获取当前执行shell脚本的 带的参数个数 $? -- 获取执行上一个指令的执行状态 ...
- Django+ajax+jsonp实现借口调用文本处理
首页 提交 <script src="/static/jquery-2.1.4.min.js"></script> <script type=&quo ...
- matlab写txt文件
fd=fopen('C:\Users\Qin\Desktop\1112.txt','w');for i=16:19 for j=1:5 fprintf(fd,'%f,%f\r\n',[cluster( ...
- nodejs socket.io初探
1.安装socket.io npm install socket.io 2.创建服务端代码server.js var app = require('http').createServer(handle ...
- VIM命令总结【转】
命令历史 以:和/开头的命令都有历史纪录,可以首先键入:或/然后按上下箭头来选择某个历史命令. 启动vim 在命令行窗口中输入以下命令即可 vim 直接启动vim vim filename 打开vim ...
- str_2.判断两个字符串是否互为旋转词
1. 字符串str的前面任意部分挪到后面形成的字符串叫做字符串str的旋转词 $str1 = "2ab1"; $str2 = "ab12"; $ret = is ...