数位dp_从模板到理解

来源:互联网 发布:淘宝手持照片怎么拍 编辑:程序博客网 时间:2024/06/05 07:14
dp是啥,我觉得dp就是记忆化的递推(递归).不同的dp的区别就在于递推(递归)的方式.
而数位dp,我觉得简单来讲就是从上一位数字推出下一位数字的状态.
在开始写数位dp之前,我推荐http://blog.csdn.net/wust_zzwh/article/details/52100392这位大佬的blog,写的真的非常非常好,看完理解它,刷刷题,数位dp就可以说是大成了.

先贴个大佬的模板,数位dp就从这个模板开始.

int a[20];  ll dp[20][state];//不同题目状态不同  ll dfs(int pos,/*state变量*/,bool lead/*前导零*/,bool limit/*数位上界变量*/)//不是每个题都要判断前导零  {      //递归边界,既然是按位枚举,最低位是0,那么pos==-1说明这个数我枚举完了      if(pos==-1) return 1;/*这里一般返回1,表示你枚举的这个数是合法的,那么这里就需要你在枚举时必须每一位都要满足题目条件,也就是说当前枚举到pos位,一定要保证前面已经枚举的数位是合法的。不过具体题目不同或者写法不同的话不一定要返回1 */      //第二个就是记忆化(在此前可能不同题目还能有一些剪枝)      if(!limit && !lead && dp[pos][state]!=-1) return dp[pos][state];      /*常规写法都是在没有限制的条件记忆化,这里与下面记录状态是对应,具体为什么是有条件的记忆化后面会讲*/      int up=limit?a[pos]:9;//根据limit判断枚举的上界up;这个的例子前面用213讲过了      ll ans=0;      //开始计数      for(int i=0;i<=up;i++)//枚举,然后把不同情况的个数加到ans就可以了      {          if() ...          else if()...          ans+=dfs(pos-1,/*状态转移*/,lead && i==0,limit && i==a[pos]) //最后两个变量传参都是这样写的          /*这里还算比较灵活,不过做几个题就觉得这里也是套路了         大概就是说,我当前数位枚举的数是i,然后根据题目的约束条件分类讨论         去计算不同情况下的个数,还有要根据state变量来保证i的合法性,比如题目         要求数位上不能有62连续出现,那么就是state就是要保存前一位pre,然后分类,         前一位如果是6那么这意味就不能是2,这里一定要保存枚举的这个数是合法*/      }      //计算完,记录状态      if(!limit && !lead) dp[pos][state]=ans;      /*这里对应上面的记忆化,在一定条件下时记录,保证一致性,当然如果约束条件不需要考虑lead,这里就是lead就完全不用考虑了*/      return ans;  }  ll solve(ll x)  {      int pos=0;      while(x)//把数位都分解出来      {          a[pos++]=x%10;//个人老是喜欢编号为[0,pos),看不惯的就按自己习惯来,反正注意数位边界就行          x/=10;      }      return dfs(pos-1/*从最高位开始枚举*/,/*一系列状态 */,true,true);//刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛  }  int main()  {      ll le,ri;      while(~scanf("%lld%lld",&le,&ri))      {          //初始化dp数组为-1         printf("%lld\n",solve(ri)-solve(le-1));      }  }  


看了这个模板,大部分人就明白了吧.
数位dp实际上就是个记忆化dfs,再判断一下是否是上界就ok了.


HDU - 2089 
数位dp大家的第一题都是no62吧.


中文题就不写题意了.
这个题就是让你理解什么叫做数位dp而已.
实际上就是先求出所有范围内的dp可能,再求我的要求的区间的dp值.
dp[位][这一位的值] = 答案


#include<cstdio>#include<cstring>#include<algorithm>using namespace std;typedef long long ll;ll f[10000][10];void getdp(){f[0][0] = 1;for(int i = 1;i < 10;i++){for(int j = 0;j < 10;j++){if(j == 4){f[i][j] = 0;}else{if(j == 6){for(int k = 0;k < 10;k++){f[i][j] += f[i - 1][k];}f[i][j] -= f[i - 1][2];}else{for(int k = 0;k < 10;k++){f[i][j] += f[i - 1][k];}}}}}return;}int a[10000];int solve(int n){a[0] = 0;while(n){a[ ++a[0] ] = n % 10;n /= 10;}a[a[0] + 1]=0;ll ans=0;for (int i = a[0];i >= 1;i--) {for (int j = 0;j < a[i];j++){if (j != 4 && !(a[i + 1] == 6 && j == 2))ans += f[i][j];}if (a[i] == 4){break;}if (a[i + 1] == 6 && a[i] == 2){break;}}return ans;}int main(){int l,r;scanf("%d%d",&l,&r);//int l = 1;//int r = 100;getdp();while(l != 0 && r != 0){ll ans1 = solve(r+1);ll ans2 = solve(l);printf("%lld\n",ans1 - ans2);scanf("%d%d",&l,&r);}return 0;}

这道题大概就这些.这题比较简单,不用套模板,自己理解着写就ok了.


CodeForces - 55D 


题意:输入n,求1到n有多少个数是b数~一个数能整除所有组成它的非0数字


这个题就可以套用内个模板了
首先先知道2520是1到9的最小公倍数,然后   

sum%(x*n)%x == sum%x
那我们就假设一个数,RX(R是已知前面位取余后的值),那么RX%2520 == (R*10+x)%2520
这样我们就得到了数位dp最重要的位置的递推关系.
len表示迭代的长度, 
num为截止当前位的数对2520取余后的值。 
lcm为截止当前位的所有数的最小公倍数。 
flag表示当前数是否可以任意取值(对取值上限进行判断)** 
则可用dp[len][num][lcm]来对其进行记录。 
然后还要注意一点,就是这道题需要离散
lcm按2520取值根本开不下,所以对lcm进行离散化,因为lcm一定可以整除2520,所以将1~2520可以整除2520的数进行标记即可

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>#include<cstdlib>#include<cmath>#include<vector>#include<functional>using namespace std;int MOD = 2520;int lcm[50];int lcmid[2555];long long dp[20][50][2555];int a[30];int gcd(int a, int b) {    return b ? gcd(b, a % b) : a;}long long dfs(int pos, int res, int lcms, int limit) {    if (pos == 0) return res % lcms == 0;    long long &ans = dp[pos][lcmid[lcms]][res];    if (!limit && ans != -1) return ans;    int top = limit ? a[pos] : 9;    long long cnt = 0;    for (int i = 0; i <= top; i++) {        //printf("dfs(%d,%d,%d,%d)\n",pos,res,lcms,limit);        int now = lcms;        if (i != 0)            now = lcms * i / gcd(lcms, i);        int nres = (res * 10 + i) % MOD;        cnt += dfs(pos - 1, nres, now, limit && i == top);    }    if (!limit) ans = cnt;    return cnt;}long long slove(long long A) {    if (A < 0) return 0;    int pos = 0;    while (A) {        a[++pos] = A % 10;        A /= 10;    }    long long ans = 0;    return dfs(pos, 0, 1, 1);}int main() {    int c = 0;    memset(dp, -1, sizeof dp);    for (int i = 1; i <= MOD; i++) {        if (MOD % i == 0) {            lcm[c] = i;            lcmid[i] = c++;        }    }    int T;    scanf("%d", &T);    while (T--) {        long long l, r;        cin >> l >> r;        cout << slove(r) - slove(l - 1) << endl;    }}

数位dp先写这么多,后面再有我再继续写




ps:人生第一篇blog哇...

原创粉丝点击