031动态规划之数位DP——算法备赛

数位DP

数位DP一般采用递归函数dfs记忆化搜索的形式。

如:dfs(int i,int mask,bool is_limit,bool is_num)

  1. i表示当前位数
  2. mask记录前面位数填写的数字的状态信息,可以是其他类型的数据
  3. is_limit表示前面所填数字是否都为n对应位上数字,若为true,则当前位所能填的最大值为s[i]-'0,否则能填的最大值为9
  4. is_num表示是前面是否填了数字(前面是否没跳过),若为true,则当前位可以从0开始填,否则从1开始填或者也跳过

数位dp的一般流程是

依次递归搜索每一位,每一位再遍历可能填写的数码,

以范围[0,123]为例,第一位可填[0,1],当第一位填了0,第二位可填范围[0,9],当第一位填了1,第二位只能填[0,2]

依次类推~

也就是说当前位能填的最大值,取决于前面所填的数码是否与目标n值对应位上的数码异议对应,若一一对应,则所填最大值不能超过n的当前位的数码值。

大部分数位问题 需要对前导0做处理,所以常用一个boolis_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为数字的位数),效率极高!

统计强大整数数目

问题描述

给你三个整数 startfinishlimit 。同时给你一个下标从 0 开始的字符串 s ,表示一个 整数。

如果一个 整数 x 末尾部分是 s (换句话说,sx后缀 ),且 x 中的每个数位至多是 limit ,那么我们称 x强大的

请你返回区间 [start..finish] 内强大整数的 总数目

如果一个字符串 xy 中某个下标开始(包括 0 ),到下标为 y.length - 1 结束的子字符串,那么我们称 xy 的一个后缀。比方说,255125 的一个后缀,但不是 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);
    }
相关推荐
_Li.16 小时前
Simulink - 6DOF (Euler Angles)
人工智能·算法·机器学习·游戏引擎·cocos2d
岛雨QA16 小时前
树结构实际应用「Java数据结构与算法学习笔记10」
数据结构·算法
zephyr0516 小时前
DP 从放弃到拿捏:一份持续更新的动态规划题解清单(一)
算法·动态规划
岛雨QA16 小时前
树结构的基础部分「Java数据结构与算法学习笔记9」
数据结构·算法
会编程的土豆16 小时前
2.25 做题
数据结构·c++·算法
Frostnova丶16 小时前
LeetCode 1356. 根据数字二进制下1的数目排序
数据结构·算法·leetcode
GEO行业研究员16 小时前
AI是否正在重构个体在健康相关场景中的决策路径——基于系统建模与决策链条结构分析的讨论
人工智能·算法·重构·geo优化·医疗geo·医疗geo优化
岛雨QA16 小时前
哈希表「Java数据结构与算法学习笔记8」
数据结构·算法
独自破碎E16 小时前
【DFS】BISHI76 迷宫寻路
算法·深度优先
寄存器漫游者16 小时前
Linux 线程间通信
数据库·算法