數位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閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容

  • 背景 一年多以前我在知乎上答了有關LeetCode的問題, 分享了一些自己做題目的經驗。 張土汪:刷leetcod...
    土汪閱讀 12,763評論 0 33
  • 動態規劃(Dynamic Programming) 本文包括: 動態規劃定義 狀態轉移方程 動態規劃算法步驟 最長...
    廖少少閱讀 3,319評論 0 18
  • 一. 增強學習簡介 1.1 什么是增強學習? 機器學習的算法可以分為三類:監督學習,非監督學習和增強學習。 增強學...
    阿阿阿阿毛閱讀 31,289評論 0 25
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,729評論 18 399
  • 今天,很要好很要好的朋友跟我說 :不要再給我發消息了 。 我的心里似乎也沒有了以前那種咯噔一下的感覺。 爽快的回復...
    楓寒鋪的老板娘閱讀 145評論 0 1