数位dp浅谈(hdu3555)
数位dp简介:
数位dp常用于求区间内某些特殊(常关于数字各个数位上的值)数字(比如要求数字含62,49);
常用解法:
数位dp常用记忆化搜索或递推来实现;
由于记忆化搜索比较好写再加上博主比较蒟,所以本文基本只介绍用记忆化搜索实现的数位dp;
记搜写法:
一般记搜写法会暴力搜索每个数的每一位,如果满足特征就加入答案;
而搜索中或搜完后用一个dp数组来存某一区间的特殊数的数量,防止多次重复搜索TLE;
空口说比较苍白无力,举个例子:比如要在1到r中找含49(4和9要连在一起)的特殊数的数量;
搜索时,传递当前要填的数字在数中的位置(pos),上一个填的数值(pre),之前有没有出现49(have),以及填的数有没有限制(limit);
pos用来观察这个数有几位,有没有达到范围;
pre用来判断之前的数是不是4,从而来判断如果当前位填9,是否能出现49
have用记录是否出现 49,在搜的过程中就可以记录特殊数的数量;
limit的作用是防止数过大超过范围(具体操作见下文例题);
dp[pos][pre]存当当前要填的数的位置为pos,上一个数为pre时特殊值的数量;
例题(hdu3555):
题目大意是给出n,给出n个范围1—r,输出每个范围中含49的数的数量
思路就是上面的例子,这里仔细介绍一下limit的用法;
比如r=1234,当pos=3(pos=1时是个位,从第一位开始搜),如果pre=1,那么这一位就只能填0—2了,limit就是记录之前填的数和上界是否相同,而传递也很简单,如果limit=true而且当前要填的数等于给定范围的pos位上的数时,limit仍然是true,否则就是false;
注意!
1、数位dp基本上的题都要开long long,不然暴力就能过了;
2、具体题目时要注意dp的含义防止重复加;
下面附上丑陋的代码:
1 #include<cstdio> 2 using namespace std; 3 #define int long long 4 const int MAXN=20; 5 int n,r,t,digit[MAXN],dp[MAXN][MAXN]; 6 //digit是上界各个位置的数 7 //dp记录搜过的值 8 int dfs(int pos,int pre,bool limit) 9 //我这里的记搜和上面讲的略有不同,求的是不满足条件的数,如果出现49了就不继续做 10 //最后答案就是上界减去搜出来的数值 11 //这样可以在记搜时去除一维,加快一点速度 12 { 13 if(pos==0) return 1; 14 if(!limit&&dp[pos][pre]!=0) 15 //这里的!limit是因为如果当前填的数是有范围的(不能大于上界),就不满足一般的规律 16 { 17 return dp[pos][pre]; 18 } 19 int up=9; 20 if(limit) up=degit[pos]; 21 //如果有限制就把上界设为范围的值 22 int ans=0; 23 for(int i=0;i<=up;++i) 24 if(pre==4&&i==9) 25 continue; 26 //满足条件就跳出 27 else 28 { 29 ans+=dfs(pos-1,i,limit&&(i==digit[pos])); 30 } 31 if(!limit) 32 //和上面的!limit同一个道理 33 { 34 dp[pos][pre]=ans; 35 } 36 return ans; 37 } 38 void solve(int x) 39 { 40 t=0; 41 int xx=x; 42 while(x>0) 43 { 44 ++t; 45 digit[t]=x%10; 46 x=x/10; 47 } 48 printf("%lld\n",xx-dfs(t,0,1)+1); 49 } 50 main() 51 { 52 scanf("%lld",&n); 53 for(int i=1;i<=n;++i) 54 { 55 scanf("%lld",&r); 56 solve(r); 57 } 58 }