数位DP学习笔记

数位dp似乎是通过加状态实现的记忆化搜索

一、 题目和暴力

数位dp的题目一般是这样的:求出区间 \([l,r]\) 中含有/不含数字 \(x\) 的数的个数。由此引申出来的还有 “从左到右各位不降” 或者 “相邻两位差为2” 等等。区间长度一般比较大,一般是 \(1 \leq l \leq r \leq 2^{31}-1\) 或者 \(1 \leq l \leq r \leq 2^{63}-1\)

显然的思路是转化为类似于前缀和的东西,这样就把问题转化为对于 \([1,n]\) 求了。

暴力1: \(O(n)\) 扫一遍区间,对于每个数 \(O(lgn)\) 扫一遍看看符不符合。时间复杂度 \(O(nlgn)\) ,不TLE就见鬼了。

暴力2: dfs每一位放什么数,时间复杂度\(O(n)\),少了个 lg 的原因是进入下一层的时候可以保证合法,但是没有本质区别。

对于暴力2,有一个小问题:怎么保证合法呢?加状态——前导零、前一位、前两位……

二、 优化

对于每一位,往下搜索时会出现大量重复状态。比如,第一位填了 1 ,第二位要考虑一遍 0~9 ;第一位填 2 的时候,第二位还是要考虑 0~9 。既然如此,为什么不记忆化呢?

再考虑有哪些状态显然不行:如果区间是 \([1,3567]\) ,第一位显然不能填大于 3 的数。那第二位呢?如果第一位填了 3 ,那第二位不能填大于 5 的数,但如果第一位是 2 ,那第二位就没有限制了。

一个方法出现了,记录下每一位的上限 \(a_i\) , 在dfs时记录状态 \(lim\) 并转移。如果上一位填到上限,下一位就不能填超

三、 代码实现和注意事项

注意事项: 记忆化数组开多大?每增加一个状态是不是就要加一维?求得是满足的还是不满足的?

U163166 Bomb

#include<stdio.h>
#include<string.h>
typedef long long ll;
int a[20];ll f[20][10];
ll dfs(int len,bool lim,int pre){
	if(!len) return 1;
	if(!lim&&f[len][pre]!=-1) return f[len][pre];
	int up=lim?a[len]:9;ll ans=0;
	for(int i=0;i<=up;++i){
		if(pre==4&&i==9) continue;
		ans+=dfs(len-1,lim&&i==up,i);
	} 
	if(!lim) f[len][pre]=ans;
	return ans;
}
inline ll calc(ll x){
	memset(a,0,sizeof a);
	memset(f,-1,sizeof f);
	int len=0;
	while(x) a[++len]=x%10,x/=10;
	return dfs(len,1,0);
}
int main(){
	int t;scanf("%d",&t);
	while(t--){
		ll n;scanf("%lld",&n);
		printf("%lld\n",(ll)n-calc(n)+1);
	}
	return 0;
}

T130742 数字游戏

#include<stdio.h>
#include<string.h>
int a[11],f[11][11];
int dfs(int len,bool lim,int pre){
	if(!len) return 1;
	if(!lim&&f[len][pre]!=-1) return f[len][pre];
	int ans=0,up=lim?a[len]:9;
	for(int i=pre;i<=up;++i)
		ans+=dfs(len-1,lim&&i==up,i);
	if(!lim) f[len][pre]=ans;
	return ans;
}
inline int calc(int x){
	memset(a,0,sizeof a);
	memset(f,-1,sizeof f);
	int len=0;
	while(x) a[++len]=x%10,x/=10;
	return dfs(len,1,0);
}
int main(){
	int a,b;
	while(scanf("%d%d",&a,&b)==2)
		printf("%d\n",calc(b)-calc(a-1));
	return 0;
}

P2657 [SCOI2009] windy 数

#include<stdio.h>
#include<string.h>
#include<math.h>
int a[11],f[11][11][2];
int dfs(int len,bool lim,int pre,bool zero){
	if(!len) return 1;
	if(!lim&&f[len][pre][zero]!=-1) return f[len][pre][zero];
	int ans=0,up=lim?a[len]:9;
	for(int i=0;i<=up;++i){
		if(abs(i-pre)<2&&!zero) continue;
		ans+=dfs(len-1,lim&&i==up,i,zero&&!i);
	}
	if(!lim) f[len][pre][zero]=ans;
	return ans;
}
inline int calc(int x){
	memset(a,0,sizeof a);
	memset(f,-1,sizeof f);
	int len=0;
	while(x) a[++len]=x%10,x/=10;
	return dfs(len,1,0,1);
}
int main(){
	int a,b;
	scanf("%d%d",&a,&b);
	printf("%d\n",calc(b)-calc(a-1));
	return 0;
}

THE END

posted @ 2021-08-13 20:43  q0000000  阅读(50)  评论(0编辑  收藏  举报