您的位置:首页 > 其它

数位DP小结_记忆化搜索版

2015-07-28 09:06 381 查看
之前写数位dp是用dp方程递推形式写,接触记忆化搜索版后,发现使用dfs更加清晰明了,思路很顺,代码简洁的多W。

首先上模板,以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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: