数位dp

数位dp#

简介#

数位 dp 是一种在数位上进行的 dp,通常用于解决值域 [L,R] 中有几个数满足条件,且 [L,R] 极大 (如 1LR1e18) 的问题,这时我们就会在数位上进行 dp,问题规模变为 lgR

数位 dp 就是一次考虑数的每一位,且从高到低枚举 (因为高位限制低位的取值范围)

我们通常用 lim 变量来表示更高的数位是否都与 R 的对应位相同

如:R=123,若前面两位取的 12,那第 3 位只能为 03,若不是,则可以取 09

我们通常使用记忆化搜索来实现数位 dp

数位 dp 模板:

inline ll dfs(int pos,int sum,int lim,...){
	if(!pos) {...}//数字填完了
	if(~f[pos][sum][rm][lim]) return f[pos][sum][rm][lim];//记忆化
	int mx=lim?a[pos]:9;//当前位最大是多少
	ll res=0;
	for(int i=0;i<=mx;++i)
		res+=dfs(pos+1,sum+i,lim&(i==mx),...);//填数
    f[pos][sum][rm][lim]=res;//记忆化
	return res;
}

#

https://vjudge.net/contest/513260

同类分布#

以这道题为例说明一下数位 dp 的常规解法

记搜的时候我们记录 4 个变量:

pos:当前搜到第几位

sum:各位数字之和

rm:原数对当前枚举的模数 mod 取模的结果

lim:前几位是否和原数相同

这题我们的思路是枚举模数 mod,然后通过记搜记录有几个符合要求,并统计答案

#include<bits/stdc++.h>
using namespace std;

#define ll long long

int len,a[20],mod;
ll l,r,f[20][180][180][2];

inline ll dfs(int pos,int sum,int rm,int lim){
	if(pos>len&&!sum) return 0;
	if(pos>len) return (rm==0&&sum==mod);
	if(~f[pos][sum][rm][lim]) return f[pos][sum][rm][lim];
	int mx=lim?a[len-pos+1]:9;
	ll res=0;
	for(int i=0;i<=mx;++i)
		res+=dfs(pos+1,sum+i,1ll*(1ll*rm*10ll+i)%mod,lim&(i==mx));
    f[pos][sum][rm][lim]=res;
	return res;
}

inline ll solve(ll x){
	len=0;
	do{
		a[++len]=x%10;
		x/=10;
	}while(x);
	ll res=0;
	for(mod=1;mod<=9*len;++mod){
        memset(f,-1,sizeof(f));
		res+=dfs(1,0,0,1);
	}
	return res;
}

signed main(){
	cin>>l>>r;
	cout<<solve(r)-solve(l-1);
}

Balanced Number#

这个中心十分特殊,且只有 18 种选择,尝试从这个中心入手

我们可以枚举以哪一位为中心,就方便统计答案了

我们记搜时记录一个变量 sum 表示当前中心左边的值-中心右边的值

当枚举到最后一位且 sum=0 时就可以统计答案

然后就是常规套路了

#include<bits/stdc++.h>
using namespace std;

#define ll long long

int len,cur,a[20];
ll l,r,f[20][2800][2];

inline ll dfs(int pos,ll sum,int lim){
	if(pos>len) return (!sum);
	if(sum<0) return 0;//小剪枝
	if(~f[pos][sum][lim]) return f[pos][sum][lim];
	int mx=lim?a[len-pos+1]:9;
	ll res=0;
	for(int i=0;i<=mx;++i)
		res+=dfs(pos+1,sum+i*(cur-pos),lim&(i==mx));
	return f[pos][sum][lim]=res;
}

inline ll solve(ll x){
	if(x==-1) return 0;
    if(!x) return 1;
	len=0;
	do{
		a[++len]=x%10;
		x/=10;
	}while(x);
	ll res=0;
	for(cur=1;cur<=len;++cur){
		memset(f,-1,sizeof(f));
		res+=dfs(1,0,1);
	}
	return res-len+1;
}

signed main(){
	int T;
	cin>>T;
	while(T--){
		cin>>l>>r;
		cout<<solve(r)-solve(l-1)<<endl;
	}
}

吉哥系列故事--恨7不成妻#

这个题需要记忆化 3 个东西:

cnts,sums,sqrs,分别表示当前状态 s有多少个数满足条件这些数的和是多少平方和是多少

然后再拿平方和公式去推

反正很复杂,知道大概思路就行了

Beautiful numbers#

各个数位上的数字都是原数的因数等价于各个数位上的数字的最小公倍数都是原数的因数

我们显然需要记录各个数位上的数字的最小公倍数 (因为数组开不下)

见小套路,我们也需要记录取模的结果 rm

但是由于模数在变,我们需要一些变通

i=19i=2520

有一个小小的性质:

如果 bmoda=0 那么 cmoda=(cmodb)moda

那么我们就可以记录 rm= 原数模 2520 的值,枚举到最后一位时判断 rm0(modlcm) 是否成立即可

那就需要记录 pos (最大为 18),rmlcm (最大为 2520)

空间:19×25212×4=483013516 B=471692.9 KB=460.6MB

炸空间了

通过题解我们得知可以:lcm2520 的约数,最多只有 2520 种取值,我们开个桶记录一下就可以了

这题有多组数据,但是你记录 lim 的话每组数据要清空 dp 数组,会 TLE

所以不记 lim,这样得到的 dp 数组可以通用,不用清空,就可以通过此题

好毒瘤!好多细节!

#include<bits/stdc++.h>
using namespace std;

#define ll long long

int len,cnt,a[20],id[2521];
ll l,r,f[20][2521][50];

inline ll dfs(int pos,int rm,int lcm,int lim){
	if(!pos) return rm%lcm==0;
	if(!lim&&~f[pos][rm][id[lcm]]) return f[pos][rm][id[lcm]];
	int mx=lim?a[pos]:9;
	ll res=0;
	for(int i=0;i<=mx;++i)
		res+=dfs(pos-1,(rm*10+i)%2520,i?lcm/__gcd(lcm,i)*i:lcm,lim&(i==mx));
	if(!lim) f[pos][rm][id[lcm]]=res;
	return res;
}

inline ll solve(ll x){
	len=0;
	do{
		a[++len]=x%10;
		x/=10;
	}while(x);
	return dfs(len,0,1,1);
}

signed main(){
	memset(f,-1,sizeof(f));
	for(int i=1;i<=2520;++i)
		if(2520%i==0) id[i]=++cnt;
	ios::sync_with_stdio(0);
	int T;
	cin>>T;
	while(T--){
		cin>>l>>r;
		cout<<solve(r)-solve(l-1)<<endl;
	}
}

小套路#

有关原数的因数#

你会发现,这种题目我们需要记录一个变量 rm 表示原数对当前模数取模的结果

而且我们可能会枚举模数 (范围小的时候) 或是动态更新模数 (可能会卡空间)

作者:Into_qwq

出处:https://www.cnblogs.com/into-qwq/p/16649872.html

版权:本作品采用「qwq」许可协议进行许可。

posted @   Into_qwq  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示