算法学习之路|数位dp简要分析

数位dp一般用于处理一些和数位有关的计数问题,比如说求区间[l,r]中有多少符合条件的数,而为了减少时间复杂度,方法使用的是动态规划的思想。

举例说明:问从0到2345这些数中总共包含多少6。

数位dp的思路是:

1.由于千位是2,首先求出[0,2000)中满足条件的个数,因为此时个十百位可以任意取值,不受上界的限制。

2.之后由于千位已经到达最高,要改为考虑百位,此时百位受到上界的限制,所以之后需要求出[2000,2300)中马满足条件的个数,此时十位和个位不受限制。

3.当百位是3时,十位又受到限制…以此类推。

当然,如何计算一个整百整千的区间的满足条件的个数,也需要在代码中体现,比如这里f(2000)=2f(1000)=2(100+10f(100))=2(100+10(10+10f(10)))

以下是数位dp的模板:

 


#include<stdio.h>
#include<algorithm>
#include<iostream>
#define n 20
using namespace std;
typedef long long ll;
ll dp[n][state];//state作为状态,不同的题目可能有不同的状态个数,有时甚至需要改变dp的维数,第一维代表数的长度
ll dd[n];//将数的每一位存储到数组
ll dfs(int pos,int state,bool lead,bool limit)
/*pos表示当前的数位,此处pos=0表示个位,pos=1表示十位,以此类推
state表示状态参数,也可以有多个状态参数
lead表示前导零(一般来说只有当答案和0有关系时才需要这个参数)
limit表示当前pos位是否受限)*/
{
    if(pos==-1)//表示已经搜索结束
        return 1;//返回并不确定,结合实际情况改变
    if(!lead&&!limit&&dp[pos][state]!=-1)//在不受限的情况下如果dp已被更新,则直接返回
        return dp[pos][state];
    int now=limit?dd[pos]:9;//now用以确定搜索终点,不受限就是9,受限就是当前pos位的数值
    ll ans=0;
    for(int i=0;i<=now;i++)//注意边界
    {
        ans+=dfs(pos-1,newstate,newlead,limit&&i==now);//只有前面所有位数都受限,下一位才受限
    }
    if(!limit&&!lead)//只有当不受限时(即范围为整十,整百...),才更新dp数组,因为受限区间不能代表整区间
        dp[pos][state]=ans;
    return ans;
}

例题:

hdu2089


所有含有4或者含有62的数都是不吉利的,求一定范围内有多少吉利的数。

ac代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
ll dp[7][10];
ll dd[7];
ll dfs(int pos,int pre,bool limit)//这里前导零不影响结果,不需要lead   pre表示pos位前一位的数字
{
    if(pos==-1)
        return 1;//进行到这里说明之前位已经全部通过,返回1
    if(!limit&&dp[pos][pre]!=-1)
        return dp[pos][pre];
    int now=limit?dd[pos]:9;
    ll ans=0;
    for(int i=0;i<=now;i++)
    {
        if(i==4||i==2&&pre==6)continue;//是不吉利的数就跳过
        ans+=dfs(pos-1,i,limit&&(i==dd[pos]));
    }
     if(!limit)
        dp[pos][pre]=ans;
    return ans;
}
ll build(int n)//计算从1-n的个数
{
    int k=0;
    while(n)
    {
        dd[k++]=n%10;
        n/=10;
    }
    return dfs(k-1,0,true);
}
int main()
{
    memset(dp,-1,sizeof(dp));//初始化
    int a,b;
    while(scanf("%d%d",&a,&b),a+b)
    {
        printf("%ld\n",build(b)-build(a-1));
    }
    return 0;
}
上一篇:crontab命令详解


下一篇:Linux makefile 教程 非常详细,且易懂【转】