数位DP
数位DP一般采用递归函数dfs记忆化搜索的形式。
如:dfs(int i,int mask,bool is_limit,bool is_num)
i表示当前位数mask记录前面位数填写的数字的状态信息,可以是其他类型的数据is_limit表示前面所填数字是否都为n对应位上数字,若为true,则当前位所能填的最大值为s[i]-'0,否则能填的最大值为9is_num表示是前面是否填了数字(前面是否没跳过),若为true,则当前位可以从0开始填,否则从1开始填或者也跳过
数位dp的一般流程是
依次递归搜索每一位,每一位再遍历可能填写的数码,
以范围[0,123]为例,第一位可填[0,1],当第一位填了0,第二位可填范围[0,9],当第一位填了1,第二位只能填[0,2]
依次类推~
也就是说当前位能填的最大值,取决于前面所填的数码是否与目标n值对应位上的数码异议对应,若一一对应,则所填最大值不能超过n的当前位的数码值。
大部分数位问题 需要对前导0做处理,所以常用一个bool值is_num来标识。
数字计数
问题描述
给定两个正整数a,b,求在[a,b]的所有整数中,每个数码(digit)(0~9的数字)各出现了多少次?
1<=a<=b <=1012。
代码
cpp
//#include<bits/stdc++.h>
#include<iostream>
using namespace std;
typedef long long ll;
const int N = 15;
ll ten[N]={0}, dp[N] = {0}; //ten[i]记录10的i次方,dp[i]记录从0到最大i位数(包含前导0,保证一定有i位)中每个数码的频数
ll cnta[10] = { 0 }, cntb[10] = { 0 }; //cnt[i]统计每个数码的频数
void init() {
ten[0] = 1;
for (int i = 1; i < N; i++) {
dp[i] = i * ten[i - 1];
ten[i] = 10 * ten[i - 1];
}
}
void solve(ll x, ll* cnt) {
ll sum = 0;
int i = 1;
while (x) {
int t = x % 10;
for (int j = 0; j <= 9; j++) cnt[j] += dp[i - 1] * t;
for (int j = 1; j < t; j++) cnt[j] += ten[i - 1]; //从1开始遍历,去除前导0
cnt[t] += sum + 1; //特判最高位数字
sum = sum * 10 + t;
x /= 10;
i++;
}
}
int main() {
init();
ll a, b; cin >> a >> b;
solve(a-1, cnta);
solve(b, cntb);
for (int i = 0; i < 9; i++) cout << cntb[i] - cnta[i] << " ";
return 0;
}
时间复杂度O(10*len)(len为数字的位数),效率极高!
统计强大整数数目
问题描述
给你三个整数 start ,finish 和 limit 。同时给你一个下标从 0 开始的字符串 s ,表示一个 正 整数。
如果一个 正 整数 x 末尾部分是 s (换句话说,s 是 x 的 后缀 ),且 x 中的每个数位至多是 limit ,那么我们称 x 是 强大的 。
请你返回区间 [start..finish] 内强大整数的 总数目 。
如果一个字符串 x 是 y 中某个下标开始(包括 0 ),到下标为 y.length - 1 结束的子字符串,那么我们称 x 是 y 的一个后缀。比方说,25 是 5125 的一个后缀,但不是 512 的后缀。
代码
cpp
long long numberOfPowerfulInt(long long start, long long finish, int limit, string s) {
long long num=0,digs=1; //num为s表示的数值,digs=10^n(n为num的位数)
for(int i=0;i<s.size();i++){
num=num*10+s[i]-'0';
digs*=10;
}
auto numbers=[&](long long st)->long long{ //求[0,st]范围内的强大整数数目
long long st_dig=st/digs,st_mod=st%digs,ans=0;
long long power=1;
int i=0;
while(st_dig){
int p=st_dig%10;
if(p<=limit){
ans+=p*power;
//最高位为p时,后面有些值可能取不到,组合总数继承上一个ans
//最高位为[0,p-1]时,后面每位可取[0,limits],组合总数为p*power
//总的组合数就是ans+p*power.
if(!i&&st_mod>=num) ans++; //第一位且st_mod>=num时,少算了一个,加上.
}
else{
ans=(limit+1)*power; //此时不用加历史值,直接将最大值赋给ans
}
st_dig/=10;
power*=limit+1; //记录位权
i++;
}
if(st>=num&&ans==0) return 1; //未进入循环,但st>=num,可以构成一个强大整数
return ans;
};
return numbers(finish)-numbers(start-1);
}
统计特殊整数
问题描述
如果一个正整数每一个数位都是 互不相同 的,我们称它是 特殊整数 。
给你一个 正 整数 n ,请你返回区间 [1, n] 之间特殊整数的数目。
代码
cpp
int countSpecialNumbers(int n) {
string s=to_string(n);
int m=s.size();
vector<vector<int>>vis(m,vector<int>(1<<10,-1)); //-1表示还未存值
/*
i表示n的当前位数
mask记录前面位数填写的数字,用二进制集合表示,mask<<t&1==0 表示前面填写的数字没有填写数字t
is_limit表示前面所填数字是否都为n对应位上数字,为true,则当前位所能填的最大值为s[i]-'0,否则能填的最大值为9
is_num表示是前面是否填了数字(前面是否没跳过),为true,则当前位可以从0开始填,否则从1开始填或者也跳过
*/
auto dfs=[&](auto&& dfs,int i,int mask,bool is_limit,bool is_num)->int{
if(i==m){
return is_num;
}
if(!is_limit&&is_num&&vis[i][mask]!=-1){
return vis[i][mask];
}
int res=0;
if(!is_num){
res+=dfs(dfs,i+1,mask,false,false);
}
int d=is_limit?s[i]-'0':9;
for(int j=is_num?0:1;j<=d;j++){
if((mask>>j&1)==0){
res+=dfs(dfs,i+1,mask|(1<<j),is_limit&&j==d,true);
}
}
if(!is_limit&&is_num){
vis[i][mask]=res;
}
return res;
};
return dfs(dfs,0,0,true,false);
}