数位DP小结_记忆化搜索版
2015-07-28 09:06
381 查看
之前写数位dp是用dp方程递推形式写,接触记忆化搜索版后,发现使用dfs更加清晰明了,思路很顺,代码简洁的多W。
首先上模板,以HDU 2089 不要62为例
几点解释:
[1]:此题中state表示上一位是否为6.可根据不同问题更改state。
[2]:此处return的结果根据题目而定。此题到最后一位一定符合条件,因此return1。其他题目可根据参数来进行判断此时是否符合条件。
[3]:fp为first plcae。即之前位是否达到上限。即:若cal(4213),若第一位为4,第二位最多枚举到2,此时fp=1,digit为2。若第一位为3.第二位则可枚举到9,此时fp=1。
[4]:此处的判定条件根据题目不同而更改。state本题表示上一位是否为6,因此若此时枚举的i为6,state=1。fp的判定每题一致,若之前位到达上限,且这一位也为上限,则fp为1。
[5]:dp[i][j]表示i位,j状态所有取值满足条件的个数。显然若fp==1,不满足所有取值,因此不可更新dp值
做题套路:
1.定义dp数组。dp的维数依题意和dfs记录的状态而定。第一维为pos,为当前枚举的位数。其他维是当前取值的状态。
2.初始化。dp初始化为-1。有时题目需要初始化其他数组,如每一位的权值。CF 55 D题中,dp的一维保存lcm值,数值很大,为使得数组开的下,需要预先初始化并且离散化。
3.计算左右界L~R。最终答案一般为cal(R)-cal(L-1)。
4.cal数组内预处理每一位的上限值,记录在digit数组中。返回dfs值
5.进行dfs。
dfs参数设定:
1.若为连续2位不能出现的数字,可用state记录前一位是否出现过。如HDU 2089,HDU 3555
2.若题目要求各位和/数字的值满足某个条件,可用sum/pre记录。注意取模。如CF 55D,HDU 3652,HDU 3709,HDU 4722等
3.若题目要求记录每一位的数字状态的总数,可用n进制表示。如SPOJ 10606,用三进制的表示每个数字的状态。
枚举上限的选取:
为digit[pos]:n。n为当前数的进制。一般为十进制,n为9。个别题目例外。如poj 3252,要求各位1的个数和0的个数,那么在处理时按二进制处理每一位。
最后,记得用LL ,记得用LL, 记得用LL 。重要的事要说三遍TUT
首先上模板,以HDU 2089 不要62为例
#define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <string.h> #include <stdlib.h> #include <queue> #include <algorithm> #include <vector> #include <stack> #include <math.h> #include <iostream> #include <functional> using namespace std; #define pow(i) (1<<(i)) #define INF (1<<29) #define mem(x) memset(x,0,sizeof(x)) #define mem1(x) memset(x,-1,sizeof(x)) typedef long long LL; int dp[10][2]; int digit[20]; int dfs(int pos,bool state,bool fp)//pos为当前位数,state为当前状态[1],fp为上一位是否到达上限的标志 { if(!pos) //若计算到最后一位,return结果[2]。 return 1; if(!fp&&dp[pos][state]!=-1) //若前一位不满足上限,并且dp[pos][state]已经确定,直接return,这里体现了记忆化搜索 return dp[pos][state]; int i,fpmax,ret=0; //fpmax为当前循环的上限,ret为此时的值 fpmax=fp?digit[pos]:9; //若上一位到达上限,此时最多枚举到这一位的数值,否则枚举到最大[3] for(i=0;i<=fpmax;i++) { if(i==4||state&&i==2)//根据题意,这里是若本位为4,或者上一位为6本位为2,则不满足 continue; ret+=dfs(pos-1,i==6,fp&&i==fpmax);//否则继续递归下一位,根据题意判定state及fp[4] } if(!fp) //若前一位不是上限,即这一位可以到最大,则更新dp值[5] dp[pos][state]=ret; return ret; //返回ret } int cal(int x) { int i,len; len=0; while(x) //求出每一位 { digit[++len]=x%10; x/=10; } return dfs(len,0,1); } int main() { int n,m; int i,j; while(scanf("%d %d",&n,&m)!=EOF) { if(n==0&&m==0) break; mem1(dp);//dp数组初始化为-1.多组数据实际上只需一次初始化 printf("%d\n",cal(m)-cal(n-1)); //计算时由右届到左界减一 } return 0; }
几点解释:
[1]:此题中state表示上一位是否为6.可根据不同问题更改state。
[2]:此处return的结果根据题目而定。此题到最后一位一定符合条件,因此return1。其他题目可根据参数来进行判断此时是否符合条件。
[3]:fp为first plcae。即之前位是否达到上限。即:若cal(4213),若第一位为4,第二位最多枚举到2,此时fp=1,digit为2。若第一位为3.第二位则可枚举到9,此时fp=1。
[4]:此处的判定条件根据题目不同而更改。state本题表示上一位是否为6,因此若此时枚举的i为6,state=1。fp的判定每题一致,若之前位到达上限,且这一位也为上限,则fp为1。
[5]:dp[i][j]表示i位,j状态所有取值满足条件的个数。显然若fp==1,不满足所有取值,因此不可更新dp值
做题套路:
1.定义dp数组。dp的维数依题意和dfs记录的状态而定。第一维为pos,为当前枚举的位数。其他维是当前取值的状态。
2.初始化。dp初始化为-1。有时题目需要初始化其他数组,如每一位的权值。CF 55 D题中,dp的一维保存lcm值,数值很大,为使得数组开的下,需要预先初始化并且离散化。
3.计算左右界L~R。最终答案一般为cal(R)-cal(L-1)。
4.cal数组内预处理每一位的上限值,记录在digit数组中。返回dfs值
5.进行dfs。
dfs参数设定:
1.若为连续2位不能出现的数字,可用state记录前一位是否出现过。如HDU 2089,HDU 3555
2.若题目要求各位和/数字的值满足某个条件,可用sum/pre记录。注意取模。如CF 55D,HDU 3652,HDU 3709,HDU 4722等
3.若题目要求记录每一位的数字状态的总数,可用n进制表示。如SPOJ 10606,用三进制的表示每个数字的状态。
枚举上限的选取:
为digit[pos]:n。n为当前数的进制。一般为十进制,n为9。个别题目例外。如poj 3252,要求各位1的个数和0的个数,那么在处理时按二进制处理每一位。
最后,记得用LL ,记得用LL, 记得用LL 。重要的事要说三遍TUT
相关文章推荐
- WIN7家庭普通版升级为WIN7旗舰版
- rlwrap: No termcap nor curses library found
- ffmpeg问题及解答
- 华为笔试题
- 谷歌面试题
- HDU 2846 Repository (Trie·统计子串)
- MapReducer中的多次归约处理
- HttpClient工具类
- jQuery实现隔行变色
- 关于iOS 7.1后下itms-services在线app
- Leetcode Q8 : String to Integer (atoi)
- java的jdbc简单封装方法
- mina2源码浅析——服务端的启动
- 高精度问题之大数相乘
- c++ primer 练习题 Section 2.8
- jQuery基本选择器
- 新的征程
- 黑马程序员 4000 ——Java基础---网络编程
- Nosql、NewSQL
- 多益网络笔试题