题目链接
问在[1, x]
之间的(所有数字) 的某类性质, 这是数位DP的问题;
按照数位DP的规定, Prefix( r)
表示: 在[0 - r]
之间的所有数字
所以, 将其转换为: Prefix( x) - Prefix( 1 - 1)
,
数位DP会将[0 - r]
的所有数字, 划分为3大类: 以r = 2345
(1, 第一类: 非对齐数字, 为所有< 1000
的数, 即[0 - 999]
这些)
(2, 第二类: 对齐数字, 为所有[1000, 2344]
这些数, 可以发现, 这些数字的长度是相同的)
(3, 第三类: r = 2345
)
一般, 优先考虑, (第二类数字) 是否有解; … 因为, 有时第一类数字的问题, 可以用与 (第二类数字) 相同的方法来解决
具体的, 第二类数字, 还会继续划分成:
([1000, 1999]
)
([2000, 2099]
[2100, 2199]
[2200, 2299]
)
([2300, 2309]
[2310, 2319]
[2320, 2329]
[2330, 2339]
)
([2340, 2340]
[2341, 2341]
[2342, 2342]
[2343, 2343]
[2344, 2345]
)
… 具体规则可以回顾(数位DP)知识.
我们称, 上面这13
个区间, 我们对(第二类数字)的处理过程, 就是 对这13个区间的处理; 即, 一个区间, 是我们最小的处理单元;
这每个区间, 他们都是可以表示为: abcd
, 且某个前缀是固定的
后缀是0-9的任意
比如, 对于区间[2100, 2199]
, 他为 (固定前缀为21
, 后缀任意), 所有区间都是这样的形式
因此, Aligned( vector< int> pre, int k)
函数, 就是针对每个区间, 求解该区间的答案;
… pre
为固定前缀(没有前导零), k
为后缀长度. 该区间有10^k
个数; [2100, 2199]
对应为Aligned( {2, 1}, 2)
那么, 比如对于[abc] [000 - 999]
区间, 他的答案是多少呢?
(1, 如果abc
是非法的 即, 存在两位相同
, 则, 整个区间全为非法的; ans为0)
(2, 否则, abc
互不相同, 则后缀有: [10-3种选择, 10-4种选择, 10-5种选择]
, 即ans = 7 * 6 * 5
, 因为所有位都不同, 就类似于(排列问题).
至此, (第二类数字) 我们已解决;
看第一类数字: [0, 999]
, 他一般有两种处理方法:
(1, 尝试用Aligned (即第二类数字的处理方法)
来解决, 通过将(第一类数字) 添加前导零, 使得 (第一类数字 与 第二类数字 ) 具有相同长度
… 即, 将[0, 999]
变成 [0000, 0999]
, 即Aligned( {0}, 3)
的答案 是否等于 [0, 999]
的答案呢?
… 答案是否定的! 0001
在 Aligned
里 会按照非法的来处理, 而0001
他在[0, 999]
里对应为1
他是合法的!!!
… 因此, 不可以添加前导零;
(2, 拆分集合; 将[0, 999]
继续划分为: ([0, 0]
[1, 9]
[10, 99]
, [100, 999]
)
… 这样划分的依据在于: (每个区间, 都是等长的)
… 每个区间的长度为: 1, 9, 90, 900, ...
… 除了第一个区间[0, 0]
, 其他区间 都是: [1 后面全是0, 9 后面全是9]
的形式;
… 对于每个区间, 继续进行划分: 比如[100, 999]
, 划分为: [100, 199]
[200, 299]
[300, 399]
…, [900, 999]
(共9个)
… 这样做的好处是: 对于每个小区间[200, 299]
, 他符合Aligned
的形式, 直接调用Aligned( {2}, 2)
即可
… … 而这里, 其实对于[1000, 9999]
, 也可以自己直接算: [9种, 9种, 8, 7, ..]
, ans = 9 * 9 * 8 * 7 ...
(注意, 第一位是[1-9]
, 其他位都是[0-9]
)
题目要求的 (特殊数 所有位不同
), 那么, 数位DP 是选择计算(特殊数) 还是计算(非特殊数)呢?
… 这要看 [a b c] [000 - 999]
, 谁能在线性时间 处理这个集合的答案;
… 这里的(非特殊数), 并不是连续的
, 比如: 12341
是(非特殊数), 它是跳跃的; 这不是DP能处理的
… 而(特殊数), 每个位 都不同, 联系到(排列数)
Build_dp
记忆化; 直接暴力即可bool Is_valid( int _a){
vector< bool> vis( 10, false);
for( auto i = _a; i > 0; i /= 10){
if( vis[ i % 10]){
return false;
}
vis[ i % 10] = true;
}
return true;
}
int Aligned( const vector< int> & _pre, int _k){ //< _pre无前导零, _pre不会为空, _k可能为0
{ //* check whether `_pre` is valid
vector< bool> vis( 10, false);
for( auto i : _pre){
if( vis[ i]){
return false;
}
vis[ i] = true;
}
}
int ans = 1;
int f = 10 - _pre.size();
FOR_( i, 1, _k, 1){
ans *= f;
-- f;
}
return ans;
}
int Prefix( int _r){
if( _r < 0){ return 0;}
//--
vector< int> bits;
for( auto i = _r; i > 0; i /= 10){
bits.push_back( i % 10);
}
reverse( bits.begin(), bits.end());
if( bits.empty()){ bits.push_back( 0);}
//--
int n = bits.size();
int ans = 0;
FOR_( i, 0, n - 1, 1){
int k = n - 1 - i;
FOR_( h, 0, bits[ i] - 1, 1){
if( ( i == 0) && ( h == 0)){
FOR_( j, 0, k, 1){
if( j == 0){
ans += Is_valid( 0);
//< 此时代表[0, 0]这个区间, 即`0`这个数; 这里不能瞎写!
//< 虽然题目是[1, r]区间 不涉及0, 但我们的数位DP就是从`0`开始的
//< 而且`Prefix( 0)`会调用(他会调用`Is_valid( 0)`, 不会进入这里, 不会执行(数位二叉树)
}
else{
FOR_( hh, 1, 9, 1){
ans += Aligned( { hh}, j - 1);
}
}
}
}
else{
vector< int> pre( bits.begin(), bits.begin() + i);
pre.push_back( h);
ans += Aligned( pre, k);
}
}
}
if( Is_valid( _r)){
++ ans;
}
return ans;
}
int countSpecialNumbers(int n) {
return Prefix( n) - Prefix( 0);
}