数位DP,统计1-N中含有“49”的总数

题目:参考数位DP,统计1-N中含有“49”的总数 另一篇参考文章HDU 3555 Bomb(1-n含有“49”的数字个数)


补充于2017-1-17
我一直在考虑如何思考这种类型的问题,后来从离散数学中找到了一个思路,就是对于计算的计数,如果有可能我们希望能找到一组递推公式,然后按照递推公式进行计算,动态规划其实就是这个思路,可能我理解还是不够深刻!
针对这题我们可以定义一组递推公式。
S0: 代表所有的可能的组合数
S1: 代表所有的不含有49的组合数
S2: 代表所有的含有49的组合数

一般情况:

S0(n) = 10 ^ n;
S1(n) = S0(n) - S2(n);
S2(n) = S2(n-1) * 10 + S1(n-2);

对于整数n的最高位的情况有:

当最高的两位的整数值大于等于49的时候有:
S2(n) = S2(n-1) * 最高位的整数值 + S1(n-2)
否则有:
S2(n) = S2(n-1) * 最高位的整数值

上述的计数方法比下面讲解的思路方法要好点,就是我们找到一个公共的方法用来解决类似的问题。这里先给个代码:

#include <vector>
#include <iostream>
using namespace std;

long long Calculate(unsigned int n)
{
    vector<int> vecNum;

    for (int i = n; i > 0; i /= 10) {
        vecNum.push_back(i % 10);
    }
    int nl = vecNum.size();

    if (nl <= 1) return 0;

    vector<vector<long long> > vecMatrix(nl+1, vector<long long>(2, 0));
    vecMatrix[0][0] = 1;
    vecMatrix[0][1] = 0;
    vecMatrix[1][0] = 10;
    vecMatrix[1][1] = 0;

    // normal
    for (int i = 2; i < nl; ++i) {
        vecMatrix[i][1] = vecMatrix[i - 1][1] * 10 + vecMatrix[i - 2][0];
        vecMatrix[i][0] = (long long)(pow(10, i)) - vecMatrix[i][1];
    }

    if (vecNum[nl-1] < 4 || (vecNum[nl-1] == 4 && vecNum[nl-2] < 9)) {
        return vecMatrix[nl-1][1] * (vecNum[nl-1]+1);
    } else {
        return vecMatrix[nl-1][1] * (vecNum[nl-1]+1) + vecMatrix[nl-2][0];
    }
}

int main(int argc, char ** argv)
{
    int n = 9999;
    cout << Calculate(n) << endl;

    return 0;
}

思路:上述的链接其实已经给了一个算法的思路,但是个人觉得这个算法的思路并不是很好,第一,这个算法思路是个野路子,你得能想到这个DP方程,你才有可能继续进行下去,第二,这个里面涉及到了状态转化,代码中的状态转化也不是很容易理解。
所以,个人总结了另一套不同的算法这个算法采用了类似容斥原理的方式。
我们使用一般性的例子,比如n只能形如9, 99, 999, 9999 这样的形式。这种形式降低了解题的复杂度,针对这种变形的题目,我们可以有如下思路。

  1. 形如X49Y,其中X代表高位的序列,Y代表低位的序列,我们可以先求得Y序列中不包含49的全部序列的数的总和A(Y),然后计算序列X的序列的数的总和B(X),使用A(Y)乘上B(X)就是此时49所处的位置的所有可能取值的数的总和。
  2. 从低位开始计算每一位49的总和,然后全部相加就是所求的1-N中含有49的总数。
  3. 针对A(Y) 我们可以建立一张DP状态方程解的表。提高运行的效率。

针对上述的算法,我们仅仅需要考虑如何计算高位的B(X)的可能次数,我们使用GetHighCount函数的方法达到我们的要求。这样我们的算法就能适应输入N的要求。
另外还有个细节,需要特殊考虑最高位。

代码如下:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

long long GetHighCountForLow(int nBegin, int nEnd)
{
    return (long long)pow(10, nEnd - nBegin); 
}

long long GetLowCount(const vector<int> & vecN, int nBegin, int nEnd, vector<long long> & dp);
long long CalCountInclude49ForLow(const vector<int> & vecN, int nBegin, int nIndex, int nEnd, vector<long long> & dp)
{
    long long X = GetHighCountForLow(nBegin, nIndex);
    long long Y = GetLowCount(vecN, nIndex+2, nEnd, dp);

    return X * Y;
}

long long GetHighCount(const vector<int> & vecN, int nBegin, int nEnd)
{
    long long sum = 0L;
    for (int i=nBegin; i<nEnd; ++i) {
        sum *= 10L;
        sum += vecN[i];
    }
    if (vecN[nEnd] >= 4)
        sum += 1L;
    return sum;
}

long long GetLowCount(const vector<int> & vecN, int nBegin, int nEnd, vector<long long> & dp)
{
    if (nBegin == nEnd) return 1;
    if (nBegin > nEnd) return 0;

    if (dp[nBegin] > -1)
        return (long long)pow(10, nEnd-nBegin) - dp[nBegin];

    long long sum = 0L;
    for (int i=nBegin; i<nEnd; ++i) {
        sum += CalCountInclude49ForLow(vecN, nBegin, i, nEnd, dp);
    }
    
    dp[nBegin] = sum;
    return (long long)pow(10, nEnd-nBegin) - dp[nBegin];
}

long long CalCountInclude49(const vector<int> & vecN, int nBegin, int nIndex, int nEnd, vector<long long> & dp)
{
    long long X = GetHighCount(vecN, nBegin, nIndex);
    long long Y = GetLowCount(vecN, nIndex+2, nEnd, dp);

    return X * Y;
}

long long CalCountInclude49(long long n)
{   
    vector<int> vecN;
    for (long long i=n; i>0; i/=10) {
        vecN.push_back(i % 10);
    }

    reverse(vecN.begin(), vecN.end());

    if (vecN.size() <= 1) return 0;
    if (vecN.size() <= 2) {
        if (vecN[0] > 5
            || (vecN[0]==4 && vecN[1] ==9))
            return 1;
        else
            return 0;
    }

    vector<long long> dp(vecN.size(), -1);
    long long sum = 0L;
    for (int i=1; i<vecN.size(); ++i)
        sum += CalCountInclude49(vecN, 0, i, vecN.size(), dp);

    if (vecN[0] > 5
        || (vecN[0]==4 && vecN[1] ==9)) {
        sum += CalCountInclude49(vecN, 0, 0, vecN.size(), dp);
    }

    return sum;
}

int main(int argc, char ** argv)
{
    long long n;
    cin >> n;
    cout << CalCountInclude49(n) << endl;
    return 0;
}

我这篇文章的之所以我认为是简单的,因为我的思路相比较参考的文章,我的维度比他的低,我就用了一个一维的数组进行DP。更重要的是,如果你多次测试你会发现DP数组是一个固定的序列,这个序列就是数位(整数的位数)数含有49的个数。我们可以事先生成这张表,而不用每次进行DP计算求得。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,747评论 0 33
  • 动态规划(Dynamic Programming) 本文包括: 动态规划定义 状态转移方程 动态规划算法步骤 最长...
    廖少少阅读 3,291评论 0 18
  • 一. 增强学习简介 1.1 什么是增强学习? 机器学习的算法可以分为三类:监督学习,非监督学习和增强学习。 增强学...
    阿阿阿阿毛阅读 31,184评论 0 25
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,654评论 18 399
  • 今天,很要好很要好的朋友跟我说 :不要再给我发消息了 。 我的心里似乎也没有了以前那种咯噔一下的感觉。 爽快的回复...
    枫寒铺的老板娘阅读 142评论 0 1