【學習筆記】數位DP
阿新 • • 發佈:2018-12-09
題目
求區間[l,r]內,滿足某條件的數的個數。這個條件一般與數的組成有關,如不包括4,非0位不超過3個,等等。
解法
- 答案滿足字首性質,可以用[1,r]的答案減去[1,l-1]的答案。在求解[1,x]時,遍歷每一位數,對每一位數及之後的位求取答案。
- 在滿足條件的數字很少且方便搜尋時,可以打一個表然後直接upper_bound - lower_bound.
Codeforces 1036C. Classy Numbers
定義好看數字為含有不超過3個非0數字位的數字,1e4個詢問,每次問兩個數(1e18)之間好看數字的個數。
//經過很長時間的優化後 狀態表示:表示內有多少個非0位不超過need個的數字,之所以不用陣列是因為upper會很大,存不下。 初始狀態: 狀態轉移:
其中k為表示upper的位數,upper[0]表示upper的最高位數字。 式子第一項表示最高位為0時,第二項表示最高位小於upper最高位,最後一項代表最高位不變。 狀態優化:可以發現,演算法會重複計算非常多次全為9的upper,所以首先打一個表,,之後狀態轉移方程變為 複雜度:打表複雜度O(80),當計算solve時,狀態數O(k),狀態轉移O(1),總複雜度O(log n)實現細節:
- 額外預處理一個10的冪次表,就可以通過upper_bound來獲得位數。
- 獲得位數後再查表一次求商,就可以獲得最高位的值。
- 遞迴時不能按位數每次減一,因為會有連續0的情況,只需要統計一次。
const int NEED = 3;
ll table[22][4]={1,1,1,1};
vector<ll> power10(1,1);
ll solve(ll upper, int need)
{
if(!bit || !need) return 1;
int bit = upper_bound(power10.begin(), power10.end(), upper) - power10.begin();
return table[bit - 1][need] +
(upper / power10[bit - 1] - 1) * table[bit - 1][need - 1] +
solve(upper % power10[bit - 1], need - 1);
}
int main(void)
{
for(int i = 1; i <= 18; i++)
power10.push_back(power10.back() * 10);
for(int i = 1; i <= 19; i++)
{
table[i][0] = 1;
for(int j = 1; j <= 3; j++)
table[i][j] = table[i - 1][j] + 9 * table[i - 1][j - 1];
}
int T = read();
while(T--)
{
ll lef = read(), rig = read();
printf("%I64d\n", solve(rig, NEED) - solve(lef - 1, NEED) );
}
return 0;
}
小結
字首性質一定要利用好,但本題中是[0,r]-[0,l-1],0與1要分清楚。 數位dp的狀態表示也像普通dp那樣,直接以答案的形式表示。 因為狀態數過多,無法用陣列表示時,可以整體仍然遞迴,部分記憶化或打表以加快速度。