「学习笔记」数位DP
虽然叫 DP,但是基本所有数位 DP 题我们都可以用好打好想好理解的 记忆化搜索 来做。
记搜模板
有一个大致的记忆化搜索模板, AK ALL 数位 DP
int dfs(int len, bool lead, bool limit, ...){
if(!len) return 1; //len = 0,即所有位都搜完
if(~f[len][lead][limit]...) return f[len][lead][limit]...; //记忆化,搜过当前情况直接返回
int r = limit ? lim[len] : 9, ans = 0; //若有上限搜到当前位的限制,否则搜到9
for(int i=0; i<=r; i++){
ans += dfs(len-1, lead&&i==0, limit&&i==r, ...);
}
return f[len][lead][limit]... = ans;
}
预处理 如下:
int work(int x){
int cnt = 0;
while(x)
{
lim[++cnt] = x % 10;
x /= 10;
}
memset(f, -1, sizeof f);
return dfs(cnt, -10, true, true);
}
注意
数据范围看清,记得开 long long
解释
主要用了填数的思想。
对于很多题,我们都需要求 1~n 中有多少符合条件的数,举个例子,
有些题保证不能有前导零,需要记一下。
常见题型
连续3个6
启示录
题意:含有连续 3 个 6 的数为魔鬼数,给定
该数整除其各位数字之和
记一个
设有
long long dfs(int len, int sum, int mod, int limit){
if(!len) return sum == s and !mod ? 1 : 0;
if(f[len][sum][mod]!=-1 and !limit) return f[len][sum][mod];
long long ans = 0, m = limit ? lim[len] : 9;
for(int i=0; i<=m; i++){
ans += dfs(len-1, sum+i, (mod*10+i)%s, limit and (i == m));
}
return limit ? ans : f[len][sum][mod] = ans;
}
long long work(long long x){
int cnt = 0;
long long ans = 0;
while(x)
{
lim[++cnt] = x % 10;
x /= 10;
}
for(s=1; s<=cnt*9; s++){
memset(f, -1, sizeof f);
ans += dfs(cnt, 0, 0, 1);
}
return ans;
}
code
#include<bits/stdc++.h>
#define in(n) scanf("%lld", &n)
using namespace std;
long long L, R;
int s, T;
long long f[20][200][200], lim[200];
long long dfs(int len, int sum, int mod, int limit){
if(!len) return sum == s and !mod ? 1 : 0;
if(f[len][sum][mod]!=-1 and !limit) return f[len][sum][mod];
long long ans = 0, m = limit ? lim[len] : 9;
for(int i=0; i<=m; i++){
ans += dfs(len-1, sum+i, (mod*10+i)%s, limit and (i == m));
}
return limit ? ans : f[len][sum][mod] = ans;
}
long long work(long long x){
int cnt = 0;
long long ans = 0;
while(x)
{
lim[++cnt] = x % 10;
x /= 10;
}
for(s=1; s<=cnt*9; s++){
memset(f, -1, sizeof f);
ans += dfs(cnt, 0, 0, 1);
}
return ans;
}
int main(){
// freopen("in.in", "r", stdin); freopen("out.out", "w", stdout);
in(R);
printf("%lld\n", work(R));
return 0;
}
二进制
处理
int dfs(int len, int _0, int _1, bool limit, bool lead){
if(!len&&!lead)return _0>=_1;
if(!len)return 0;
if(~f[len][_0][_1][limit][lead]) return f[len][_0][_1][limit][lead];
int r = limit ? lim[len] : 1;
int ans = 0;
for(int i=0; i<=r; i++){
ans += dfs(len-1, (i==0&&!lead)?(_0+1):_0, (i==1)?(_1+1):_1, limit&&(i==r), lead&&(i==0));
}
return f[len][_0][_1][limit][lead] = ans;
}
int work(int x){
int cnt=0;
while(x)lim[++cnt]=x&1,x>>=1;
memset(f, -1, sizeof f);
return dfs(cnt,0,0,1,1);
}
code
#include<bits/stdc++.h>
#define int long long
#define ll long long
using namespace std;
int L, R;
int f[35][35][35][2][2];
int lim[35];
int dfs(int len, int _0, int _1, bool limit, bool lead){
if(!len&&!lead)return _0>=_1;
if(!len)return 0;
if(~f[len][_0][_1][limit][lead]) return f[len][_0][_1][limit][lead];
int r = limit ? lim[len] : 1;
int ans = 0;
for(int i=0; i<=r; i++){
ans += dfs(len-1, (i==0&&!lead)?(_0+1):_0, (i==1)?(_1+1):_1, limit&&(i==r), lead&&(i==0));
}
return f[len][_0][_1][limit][lead] = ans;
}
int work(int x){
int cnt=0;
while(x)lim[++cnt]=x&1,x>>=1;
memset(f, -1, sizeof f);
return dfs(cnt,0,0,1,1);
}
signed main(){
scanf("%lld%lld", &L, &R);
printf("%lld ", work(R) - work(L-1));
return 0;
}
各位数字都被该数本身整除
我们知道
inline int get_gvc(int gvc, int now){
if(!now) return gvc;
else return gvc * now / __gcd(gvc, now);
}
inline void yuen(){
int tot = 0;
for(int i=1; i<=2520; i++){
if(2520 % i == 0) a[i] = ++tot;
}
for(int i=0; i<=18; i++) g[i] = pow(10, i);
}
inline ll dfs(int len, int sum, int gvc, bool limit, bool lead){
if(!len) return (sum % gvc == 0);
if(~f[len][sum][a[gvc]] and !limit and !lead) return f[len][sum][a[gvc]];
int r = limit ? lim[len] : 9;
ll ans = 0;
for(int i=0; i<=r; i++){
ans += dfs(len-1, (sum*10+i)%2520, get_gvc(gvc, i), limit&&(i==r), lead&&(i==0));
}
if(limit or lead) return ans;
return f[len][sum][a[gvc]] = ans;
}
inline ll work(ll x){
int cnt = 0;
while(x){ lim[++cnt] = x % 10; x /= 10;}
return dfs(cnt, 0, 1, true, true);
}
code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll L, R;
int lim[22];
int T, a[2530];
ll f[20][2530][60], g[20];
inline int get_gvc(int gvc, int now){
if(!now) return gvc;
else return gvc * now / __gcd(gvc, now);
}
inline void yuen(){
int tot = 0;
for(int i=1; i<=2520; i++){
if(2520 % i == 0) a[i] = ++tot;
}
for(int i=0; i<=18; i++) g[i] = pow(10, i);
}
inline ll dfs(int len, int sum, int gvc, bool limit, bool lead){
if(!len) return (sum % gvc == 0);
if(~f[len][sum][a[gvc]] and !limit and !lead) return f[len][sum][a[gvc]];
int r = limit ? lim[len] : 9;
ll ans = 0;
for(int i=0; i<=r; i++){
ans += dfs(len-1, (sum*10+i)%2520, get_gvc(gvc, i), limit&&(i==r), lead&&(i==0));
}
if(limit or lead) return ans;
return f[len][sum][a[gvc]] = ans;
}
inline ll work(ll x){
int cnt = 0;
while(x){ lim[++cnt] = x % 10; x /= 10;}
return dfs(cnt, 0, 1, true, true);
}
signed main(){
//freopen("in.in", "r", stdin); freopen("out.out", "w", stdout);
memset(f, -1, sizeof f); yuen();
cin>>T;
while(T--)
{
scanf("%lld%lld", &L, &R);
printf("%lld\n", work(R) - work(L-1));
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!