模板 - 动态规划 - 数位dp

#include<bits/stdc++.h>
using namespace std;
#define ll long long

int a[20];
ll dp[20][20/*可能需要的状态1*/][20/*可能需要的状态2*/];//不同题目状态不同
ll dfs(int pos,int state1/*可能需要的状态1*/,int state2/*可能需要的状态2*/,bool lead/*这一位的前面是否为零*/,bool limit/*这一位是否取值被限制(也就是上一位没有解除限制)*/)
//不是每个题都要处理前导零
{
    //递归边界,最低位是0,那么pos==-1说明这个数枚举完了
    if(pos==-1)
        return 1;/*这里返回1,表示枚举的这个数是合法的,那么这里就需要在枚举时必须每一位都要满足题目条件,也就是说当前枚举到pos位,一定要保证前面已经枚举的数位是合法的。 */
    //第二个就是记忆化(在此前可能不同题目还能有一些剪枝)
    if(!limit && !lead && dp[pos][state1][state2]!=-1)
        return dp[pos][state1][state2];
    /*常规写法都是在没有限制的条件记忆化,这里与下面记录状态对应*/
    int up=limit?a[pos]:9;//根据limit判断枚举的上界up
    ll ans=0;
    //开始计数
    for(int i=0;i<=up;i++)//枚举,然后把不同情况的个数加到ans就可以了
    {
        int new_state1=???;
        int new_state2=???;
        /*
        计数的时候用continue跳过不合法的状态,不再搜索
        */

        //合法的状态向下搜索
        ans+=dfs(pos-1,new_state1,new_state2,lead && i==0,limit && i==a[pos]);//最后两个变量传参都是这样写的
    }
    //计算完,记录状态
    if(!limit && !lead)
        dp[pos][state1][state2]=ans;
    /*这里对应上面的记忆化,在一定条件下时记录,保证一致性,当然如果约束条件不需要考虑lead,这里就是lead就完全不用考虑了*/
    return ans;
}

ll solve(ll x)
{
    //可能需要特殊处理0或者-1
    if(x<=0)
        return ???;

    int pos=0;
    while(x)//把数位分解
    {
        a[pos++]=x%10;//编号为[0,pos),注意数位边界
        x/=10;
    }

    return dfs(pos-1/*从最高位开始枚举*/,0/*可能需要的状态1*/,0/*可能需要的状态2*/,true,true);//刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛
}

int main()
{
    memset(dp,-1,sizeof(dp));
    //一定要初始化为-1

    ll le,ri;
    while(~scanf("%lld%lld",&le,&ri))
    {
        printf("%lld\n",solve(ri)-solve(le-1));
    }
}

其实另一种计数写法对别的题目有一定的启发性,需要特别注意的是,无论哪种写法的dp结果中存的数字都是和le与ri无关的。所以在数位受限时不能取用计算过的dp值,也不能更新dp值,不受限的情况可以重复利用。

无注释版:

#include<bits/stdc++.h>
using namespace std;
#define ll long long

int a[20];
ll dp[20][MAXS1][MAXS2];
ll dfs(int pos,int s1,int s2,bool lead,bool limit) {
    if(pos==-1) {
        return ?;
    }
    if(!limit && !lead && dp[pos][s1][s2]!=-1)
        return dp[pos][s1][s2];
    int up=limit?a[pos]:9;
    ll ans=0;
    for(int i=0; i<=up; i++) {
        int ns1=op1(s1);
        int ns2=op2(s2);
        ans+=dfs(pos-1,ns1,ns2,lead && i==0,limit && i==a[pos]);
    }
    if(!limit && !lead)
        dp[pos][s1][s2]=ans;
    return ans;
}

ll solve(ll x) {
    if(x<=0)
        return ?;

    int pos=0;
    while(x) {
        a[pos++]=x%10;
        x/=10;
    }

    return dfs(pos-1,INITS1,INITS2,true,true);
}

int main() {
    memset(dp,-1,sizeof(dp));

    ll le,ri;
    while(~scanf("%lld%lld",&le,&ri)) {
        printf("%lld\n",solve(ri)-solve(le-1));
    }
}


一个更简单的模板,去掉了很多奇奇怪怪的东西,比如前导0,前导0的确应该特殊考虑而不能一概而论。

int dfs(int i, int s, bool e) {
    if (i==-1) return s==target_s;
    if (!e && ~f[i][s]) return f[i][s];
    int res = 0;
    int u = e?num[i]:9;
    for (int d = first?1:0; d <= u; ++d)
        res += dfs(i-1, new_s(s, d), e&&d==u);
    return e?res:f[i][s]=res;
}

看起来清爽多了,其中:

f为记忆化数组;

i为当前处理串的第i位(权重表示法,也即后面剩下i+1位待填数);

s为之前数字的状态(如果要求后面的数满足什么状态,也可以再记一个目标状态t之类,for的时候枚举下t);

e表示之前的数是否是上界的前缀(即后面的数能否任意填)。

for循环枚举数字时,要注意是否能枚举0,以及0对于状态的影响,有的题目前导0和中间的0是等价的,但有的不是,对于后者可以在dfs时再加一个状态变量z,表示前面是否全部是前导0,也可以看是否是首位,然后外面统计时候枚举一下位数。

注意:

不满足区间减法性质的话,不能用solve(r)-solve(l-1)。



看了学长的部分博客之后发现其实使用f[i][j][st]表示以j开头的i位数满足条件st的数的个数也是可以的。待更新。

原文地址:https://www.cnblogs.com/Yinku/p/10416210.html

时间: 2024-10-24 19:50:10

模板 - 动态规划 - 数位dp的相关文章

【luogu2657】【bzoj1026】 [SCOI2009]windy数 [动态规划 数位dp]

P2657 [SCOI2009]windy数 bzoj1026 一本通说这是一道数位dp模板题 emmmmm 就是逐位确定 f[i][j]表示填了i位数其最高位数字为j 然后就去求可能方案数 分为 不满足x的位数的严格小于x的全部情况 和x的位数相同 但最高位小于x的最高为的全部方案数 和x的位数相同 有一位比x的对应位数小的全部方案数 其余位数对应数字都相同(这是数位dp常用的一个性质:对于一个小于n的数 它从高位到低位一定会出现某一位上的数字小于n所对应这一位上的数字) PS 因为x不一定为

[动态规划][数位dp]不要62

Problem Description 杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer).杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众.不吉利的数字为所有含有4或62的号码.例如:62315 73418 88914都属于不吉利号码.但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列.你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多

[动态规划][数位dp]Bomb

Problem Description The counter-terrorists found a time bomb in the dust. But this time the terrorists improve on the time bomb. The number sequence of the time bomb counts from 1 to N. If the current number sequence includes the sub-sequence "49&quo

动态规划-数位dp

大佬讲的清楚 [https://blog.csdn.net/wust_zzwh/article/details/52100392] 例子 不要62或4 l到r有多少个数不含62或者4 代码 #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; int a, b,shu[20],dp[20][2]; int dfs(i

数位dp小记

转载自:http://blog.csdn.net/guognib/article/details/25472879 参考: http://www.cnblogs.com/jffifa/archive/2012/08/17/2644847.html kuangbin :http://www.cnblogs.com/kuangbin/category/476047.html http://blog.csdn.net/cmonkey_cfj/article/details/7798809 http:/

有关动态规划(主要是数位DP)的一点讨论

动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法.20世纪50年代初美国数学家在研究多阶段决策过程的优化问题时,提出了最优化原理,把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法--动态规划. ——以上内容,节选自http://baike.so.com/doc/6995222-7218096.html <<<<<<<<

数位dp模板 [dp][数位dp]

现在才想到要学数位dp,我是不是很弱 答案是肯定的 以一道自己瞎掰的题为模板 1 //题: 2 //输入数字n 3 //从0枚举到n,计算这n+1个数中含有两位数a的数的个数 4 //如12930含有两位数93 5 #include<cstdio> 6 #include<cstring> 7 #include<iostream> 8 using namespace std; 9 10 int n,m=0,a,g1,g2; 11 int dp[10][10][2],lim

数位dp小结以及模板

这里是网址 别人的高一啊QAQ.... 嗯一般记忆化搜索是比递推好写的所以我写的都是dfs嗯......(因为我找不到规律啊摔,还是太菜.....) 显然这个东西的条件是非常的有套路..但是不管怎么样我就抄这个大佬的模板好了.... 找到合适的递推式是很重要的(都是废话) 一般数位dp都是和整除和各位数组成有关的.... 没了.....

51nod 1009 - 数字1的数量 - [数位DP][模板的应用以及解释]

题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1009 基准时间限制:1 秒 空间限制:131072 KB 给定一个十进制正整数N,写下从1开始,到N的所有正数,计算出其中出现所有1的个数. 例如:n = 12,包含了5个1.1,10,12共包含3个1,11包含2个1,总共5个1. Input 输入N(1 <= N <= 10^9) Output 输出包含1的个数 Input示例 12 Output示例