算法:數位DP

數位DP

問題

求區間[L,R][L, R]中滿足條件的數有多少個, 0LR0 \le L \le R,該條件與數位有關,比如不包含數字44

思路

考慮函數cal(n)cal(n)表示區間[0,n][0, n]中滿足條件的數的個數,那麼區間[L,R][L, R]中滿足條件的數的個數爲cal(R)cal(L1)cal(R) - cal(L-1)
然後用動態規劃的方法求cal(n)cal(n)。求出nn的每一位數字以及長度lenlen,用數組dp[i][j]dp[i][j]表示長度爲ii首位爲jj的滿足條件的數的個數,那麼dp[i][0]dp[i][0]表示長度爲ii的滿足條件的後綴的個數,而dp[len][0]dp[len][0]表示所有長度小於lenlen的滿足條件的數的個數,令ans=dp[len][0]ans = dp[len][0]
最後求出所有長度爲lenlen的滿足條件的數的個數。對於nn從高位到低位,計算所有最高的(i1)(i-1)位與nn相同且第ii位比nn小的滿足條件的數的個數,設nn的第ii位數字爲did_i,則ans=ans+0di1dp[i][di]ans = ans + \sum^{d_i-1}_0 dp[i][d_i],如果nn從第ii位開始不滿足條件就退出循環。若nn本身滿足條件,則ans=ans+1ans = ans + 1。那麼cal(n)=anscal(n) = ans

時間複雜度

O(logn)O(\log n)。實際上爲nn的位數,而nn的位數可以表示爲lgn\lg n,即lognlog10\frac {\log n}{\log 10}log\log默認爲以22爲底的對數。

測試

HDU: 2089
LeetCode: 902

模板

#include <iostream>
using namespace std;
#include <cstdio>
#include <cstring>

typedef long long LL;
const int N = 20;  // the maximal number of digits


LL dp[N][10];  // dp[i][j] is the amount of numbers with i digits and first digit j
int nums[N];  // the digits of n

/**
  * @param n: the maximal number
  * @return: the amount of numbers from 0 to n
  */
LL cal(LL n) {
  int len = 0;  // the length of n

  // nums[i] is the i-th digit of n from right to left
  while (n > 0) {
    nums[++len] = n % 10;
    n /= 10;
  }

  memset(dp, 0, sizeof(dp));
  for (int i = 1; i <= len; ++i) {
    for (int j = 0; j <= 9; ++j) {
      /*
        update: dp[i][j]
      */
    }
  }

  LL ans = dp[len][0];  // the numbers with length shorter than n
  dp[len][0] = 0;
  int pos;  // the position of the digits of n
  for (pos = len; pos >= 1; --pos) {
    for (int j = 0; j < nums[pos]; ++j) {
      /*
        condition: continue
      */
      ans += dp[pos][j];  // the numbers less than n
    }
    /*
      condition: break
    */
    if (dp[pos][nums[pos]] == 0) break;  // no more numbers
  }
  if (pos == 0) ++ans;  // n

  return ans;
}

參考

數位DP講解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章