一、题意

给定一个区间[a, b](注意输入的时候可能a > b,所以,在数据输入后,要先比较a和b,如果a > b,交换a和b的值),统计这个区间里面,数位上有多少个0、多少个1、……、多少个9。

二、思路

第一种:数位DP。dfs函数的参数列表为:

int pos:当前处理的数位所在的位置;

int val:当前统计的数值(0——9);

int amt:从最高位开始到当前位置的前一个位置(因为当前位置还没开始统计),val的数量。

bool lead:是否有前导0。因为这题要统计0的个数,前导0对结果是有影响的,所以需要做限制。

bool limit:当前数位的最大值是否受前一位限制。

dp[15][10][15]:记忆化当前从当前状态出发往下搜索得到的结果。dp[pos][val][amt]表示:从 ”从最高位到当前位pos的前一个位置为止,待统计数值为val,已统计的val的个数为amt(是从最高位到当前位置pos的前一个位置已统计的val的个数)“ 的这种状态开始往下搜索所能得到的结果(这个结果就是从pos位置开始搜索到最后一个位置所能得到的val的个数。注意,是搜索,而不是对某一个特定的数)。没明白的再重新看一遍这段话,再次强调,我们的dp数组、amt等变量做的统计都是从最高位到当前位的前一个位置之间的。因为当前位置pos的这个数位上的值还没有开始统计。

关于上述的dp数组的设计,我之前看过别人的一篇博客,还和博主讨论一番,当时也是迷迷糊糊似懂非懂的。今天再次做这个题,终于明白了为什么这样设计是正确的。当limit == false && lead == false时(这是dp记忆化的前提),不管你前面(从最高位到当前位的前一个位置之间)的amt个val值怎么排列,只要你到当前位置pos时limit == false而且lead
== false,那么,从当前位置pos一直到最低位之间的数值都可以进行任意的排列组合,自然搜索得到的结果的相同的,所以可以用记忆化最开始第一次搜索得到的结果。比如:当前pos在最高位的后三个位置,统计的数值是0,不管你的前缀是110……还是101……,只要你的limit == false而且lead == false,那后面的所有数位就可以任意排列组合,那么,当我第一次已110……前缀往下搜索得到结果以后,使用dp数组记忆化一下,再当我用101……前缀去往下搜索时,得到的结果肯定和以110……前缀往下搜索得到的结果相同,所以可以直接去dp数组里面取出结果。

第二种:网上最为流行的,按权来统计数位。

三、源代码

第一种:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<cctype>
#include<algorithm>
#include<utility>
#include<vector>
#include<set>
#include<map>
#include<stack>
#include<queue>
using namespace std;
typedef __int64 LL;

int digit[15], dn;
LL dp[15][10][15], ans[2][10];

LL dfs(int pos, int val, int amt, bool lead, bool limit) {
    if(pos == 0)return amt;
    if(!limit && !lead && dp[pos][val][amt] != -1)return dp[pos][val][amt];
    int top = limit ? digit[pos] : 9, t = 0;
    LL ans = 0;
    for(int i = 0; i <= top; ++i) {
        if(val != i)t = amt;
        else {
            if(val == 0) {
                if(lead)t = 0;
                else t = amt + 1;
            } else t = amt + 1;
        }
        ans += dfs(pos - 1, val, t, lead && i == 0, limit && i == top);
    }
    if(!limit && !lead)dp[pos][val][amt] = ans;
    return ans;
}

void solve(LL x, int idx) {
    if(x == 0)return;
    memset(digit, 0, sizeof(digit));
    dn = 0;
    for(; x > 0; x /= 10) digit[++dn] = x % 10;
    for(int i = 0; i < 10; ++i) ans[idx][i] = dfs(dn, i, 0, true, true);
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
#endif // ONLINE_JUDGE
    memset(dp, -1, sizeof(dp));
    LL a, b;
    while(scanf("%I64d%I64d", &a, &b), a != 0 || b != 0) {
        if(a > b)swap(a, b);
        memset(ans, 0, sizeof(ans));
        solve(a - 1, 0), solve(b, 1);
        for(int i = 0; i < 10; ++i)printf("%I64d%c", ans[1][i] - ans[0][i], i < 9 ? ' ' : '\n');
    }
    return 0;
}

第二种思路的代码暂时先不贴上来。

四、对数位dp使用dfs方式的思考与总结

使用dfs方式搜索结果,实际上就是一棵十叉树,每一条从根节点到叶子节点的路径都是一个具体的数值。每一层节点都有一个层号,也就是我们的pos。当dfs的参数给定时,可以确定这棵树上的一个具体的节点,如果我们已经使用dp数组记忆了从这个节点开始往下搜索能得到的结果,那么,下次我们再到达这个节点时,直接返回dp数组的结果就好了。在最坏的情况下,这种记忆化的dfs会产生一棵”大十叉树“,也就是说,对于每一个给定的数值,都能在这棵树上找到一条路径。那么在这种情况下,当最顶上的根节点数值又很大时(不大的话直接用循环暴力解决得了),容易发生栈溢出以及超时(因为和暴力循环没多大区别)。所以,为了防止栈溢出,就必须做记忆化工作。

POJ-2282题解&数位DP总结的更多相关文章

  1. POJ 3252 (数位DP)

    ###POJ 3252 题目链接 ### 题目大意:给你一段区间 [Start,Finish] ,在这段区间中有多少个数的二进制表示下,0 的个数 大于等于 1 的个数. 分析: 1.很显然是数位DP ...

  2. luogu2657-Windy数题解--数位DP

    题目链接 https://www.luogu.org/problemnew/show/P2657 分析 第一道数位DP题,发现有点意思 DP求\([L,R]\)区间内的XXX个数,很套路地想到前缀和, ...

  3. 洛谷P2602 [ZJOI2010]数字计数 题解 数位DP

    题目链接:https://www.luogu.com.cn/problem/P2602 题目大意: 计算区间 \([L,R]\) 范围内 \(0 \sim 9\) 各出现了多少次? 解题思路: 使用 ...

  4. 洛谷P3413 SAC#1 - 萌数 题解 数位DP

    题目链接:https://www.luogu.com.cn/problem/P3413 题目大意: 定义萌数指:满足"存在长度至少为2的回文子串"的数. 求区间 \([L,R]\) ...

  5. HDU4352 XHXJ's LIS 题解 数位DP

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4352 题目大意: 求区间 \([L,R]\) 范围内最长上升子序列(Longest increasin ...

  6. HDU4507 吉哥系列故事——恨7不成妻 题解 数位DP

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4507 题目大意: 找到区间 \([L,R]\) 范围内所有满足如下条件的数的 平方和 : 不包含'7' ...

  7. HDU3709 Balanced Number 题解 数位DP

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3709 题目大意: 求区间 \([x, y]\) 范围内"平衡数"的数量. 所谓平衡 ...

  8. HDU3652 B-number 题解 数位DP

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3652 题目大意: 求区间 \([1, n]\) 范围内包含连续的数位"13"并且能 ...

  9. HDU5179 beautiful number 题解 数位DP

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5179 题目大意: 给你一个数 \(A = a_1a_2 \cdots a_n\) ,我们称 \(A\) ...

随机推荐

  1. 手游服务端框架之使用Guava构建缓存系统

    缓存的作用与应用场景 缓存,在项目中的应用非常之广泛.诸如这样的场景,某些对象计算或者获取的代码比较昂贵,并且在程序里你不止一次要用到这些对象,那么,你就应该使用缓存. 缓存跟java的Coucurr ...

  2. CS231n课程笔记翻译1:Python Numpy教程

    译者注:本文智能单元首发,翻译自斯坦福CS231n课程笔记Python Numpy Tutorial,由课程教师Andrej Karpathy授权进行翻译.本篇教程由杜客翻译完成,Flood Sung ...

  3. 如何制作dll库的API文档,自动生成微软风格的chm文件 Sandcastle Help File Builder 使用方法

    当你开发了一个库的时候,就需要给库开发一个api文档,微软提供了一个C#库的自动生成工具.我在使用的过程中记录了相关的信息,以供大家学习和查阅,如有不正之处,欢迎指出. 首先先下载一个软件,下载地址在 ...

  4. HTML字符实体(Character Entities),转义字符串(Escape Sequence) 转

    为什么要用转义字符串? HTML中<,>,&等有特殊含义(<,>,用于链接签,&用于转义),不能直接使用.这些符号是不显示在我们最终看到的网页里的,那如果我们希 ...

  5. matlab load

    参考文献:http://jingyan.baidu.com/article/fec4bce2257963f2618d8bfa.html 对应save,load 命令更加简单. load的方式有三种: ...

  6. php pdo调用SQLServer存储过程无法获取返回结果

    确定存储过程写的没问题,php调用后,跟踪了语句,也是没问题,就是获取不到返回结果.折腾,搞定. 较之前明确了1. 调用存储过程传参的写法: 2. 获取返回结果集的方法 参考: http://blog ...

  7. Codeforces 1012C Hills【DP】*

    Codeforces 1012C Hills Welcome to Innopolis city. Throughout the whole year, Innopolis citizens suff ...

  8. BZOJ2595 Wc2008 游览计划 【斯坦纳树】【状压DP】*

    BZOJ2595 Wc2008 游览计划 Description Input 第一行有两个整数,N和 M,描述方块的数目. 接下来 N行, 每行有 M 个非负整数, 如果该整数为 0, 则该方块为一个 ...

  9. Dapper.Contrib 开发.net core程序,兼容多种数据库

    Dapper.Contrib 开发.net core程序,兼容多种数据库 https://www.cnblogs.com/wuhuacong/p/9952900.html 使用Dapper.Contr ...

  10. 《DSP using MATLAB》示例Example 8.22

    代码: %% ------------------------------------------------------------------------ %% Output Info about ...