【題解】P2602 數字計數 - 數位dp


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("\n");
    return 0;
}

Thanks

如果有任何疑問歡迎提出或和我一起討論(′▽`〃)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM