算法:数位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讲解

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