zoukankan      html  css  js  c++  java
  • 【题解】P2602 数字计数

    P2602 [ZJOI2010]数字计数

    题目描述

    给定两个正整数 (a)(b) ,求在 ([a,b]) 中的所有整数中,每个数码(digit)各出现了多少次。

    输入格式

    输入文件中仅包含一行两个整数(a,b),含义如上所述。

    输出格式

    输出文件中包含一行 (10) 个整数,分别表示 (0-9)([a,b]) 中出现了多少次。

    说明/提示

    (30\%)的数据中,(a<=b<=10^6)

    (100\%)的数据中,(a<=b<=10^{12})


    Solution

    此题解仅讲想法,不讲有关数位 (dp)的基础知识或写法,如果还没有学过数位 (dp) 的可以先看别的题目

    看到题解里用 (dfs) 做的都是设的两维或以上的状态,实际上这道题只需要一维状态就够了

    设目前已经填到了第 (pos) 位,则不管 (num -!- pos + 1) 位上填的是什么,之后的 (1 -!- pos)位上的贡献是不变的(除非是已经到了 (limit) 的限制了,这个之后再讨论)

    那么状态很明显为 (f[pos])

    (eg:)现在要填五位的数,目前状态为 (12XXX)(limit)(30000),则后面的三位可以直接由 (f[3]) 转移过来,因为这属于子结构,不对前面造成影响

    为什么可以这样转移?

    前面所填的数(类似于 (eg) 中的 (12XXX)(12))的贡献如何计算?

    我们可以发现,对于 (12XXX) 中 第四位上的 (2) 的贡献,是 (10^{pos - 1}) 的。因为 (12XXX) 的后三位可以填 (000) - (999) 中的任意一种,则第四位的 (2) 就被计算了 (10^3) 次,即贡献就是 (10^{pos - 1})注意:这里所讨论的 (2) 的贡献值,仅考虑第四位上的 (2) ,对于后面位置上的为子结构,在之后会考虑到,而前面位置上的,在之前已经预先考虑过了,所以不会重复也不会漏情况。

    现在再来讨论 (limit) 的限制情况。

    假设将 (eg) 中的 (limit) 改为 (12300),则填后三位时就只能填 (000) - (300),总共是 (12300 - 12000 + 1) 种,于是只用在计算贡献时加这样一个判断就可以了。


    Code

    #include<bits/stdc++.h>
    #define ll long long
    #define F(i, x, y) for(int i = x; i <= y; ++ i)
    using namespace std;
    const int N = 15;
    ll L, R;
    int cnt[N];
    ll f[N];
    ll add(int pos)//计算lim限制时的贡献
    {
        ll ans = 0;
        for(int i = pos - 1; i >= 1; -- i) ans = ans * 10 + cnt[i];
        return ans + 1; 
    }
    ll dp(int pos, int x, int lim, int last)
    {/* pos为第几位  x为现在在算的数码
    	lim为是否为限制 last为上一次的值(处理前导零)*/
        if(! pos) return 0;
        if(! lim && f[pos] != -1 && last != 10) return f[pos];
        ll ret = 0;
        F(i, (last == 10 ? 1 : 0), (lim ? cnt[pos] : 9))
        {
            if(i == x && (i != cnt[pos] || ! lim)) ret += pow(10, pos - 1);
            else if(i == x) ret += add(pos);//分情况计算贡献
            ret += dp(pos - 1, x, lim && i == cnt[pos], i);
        }
        if(last == 10) ret += dp(pos - 1, x, 0, last);
        if(! lim) f[pos] = ret;
        return ret;
    }
    ll work(int x, ll r)
    {
        memset(f, -1, sizeof(f));
        int num = 0;
        for(r; r; r /= 10) cnt[++ num] = r % 10;
        return dp(num, x, 1, 10);
    }
    int main()
    {
        cin >> L >> R;
        F(i, 0, 9) printf("%lld ", work(i, R) - work(i, L - 1));
        printf("
    ");
        return 0;
    }
    

    Thanks

    如果有任何疑问欢迎提出或和我一起讨论(′▽`〃)

  • 相关阅读:
    SpringCloud Alibaba整合Sentinel
    Jmter入门教程
    惊!!!笔记本外接显示器,显示器界面不能充满全屏
    js-使用attr()方法
    关于JS的assign() 方法
    《转》webpack+vue+vueRouter模块化构建完整项目实例超详细步骤(附截图、代码、入门篇)
    元素水平垂直居中的四种方式(别人文章)
    关于截取字符串substr和substring两者的区别
    JavaScript三种弹出框(alert,confirm和prompt)用法举例
    ::before和::after伪元素的用法
  • 原文地址:https://www.cnblogs.com/Bn_ff/p/12185028.html
Copyright © 2011-2022 走看看