数位DP浅谈

【参考资料:信息学奥赛一本通 提高篇】

为什么说是浅谈呢?因为楼主对于数位dp也不过只有很浅薄的理解,也就只能浅谈一下了。。。

一、简介

“在信息学竞赛中,有一类与数位有关的区间统计问题。这类问题往往具有比较浓厚的数学味道,无法暴力求解,需要在数位上进行递推等操作。”——刘聪《浅谈数位类统计问题》

  • 对于这样的问题,我们需要借用DP的思想,以数位为阶段,在数位上进行递推,这就是数位DP

二、一个例子

  • 这里以一道经典例题作为例子来进行讲解 HDU2089 不要62
  • 题意:问在区间 [ l , r ] [l,r] [l,r]中有多少个数既不包含4也不包含62
  • 解析:数位dp入门题
  • 基本思想:
    • 预处理 f f f数组, f [ i , s ] f[i,s] f[i,s]代表位数为i(可能允许前导0。如00058也是个5
      位数),状态为 s s s的方案数。这里 s s s根据题目需要确定。
    • 例如 f [ 4 , s ] f[4,s] f[4,s]表示0-9999中所有符合条件的数的个数
    • 显然有 f [ i , s ] = f [ i − 1 , s 1 ] , 其 中 s 1 是 对 应 的 状 态 f[i,s]=f[i-1,s1],其中s1是对应的状态 f[i,s]=f[i1,s1],s1
  • 在本题中 f [ i ] [ j ] f[i][j] f[i][j]表示位数为i最高位为j的数中有多少个符合条件
  • f [ 2 , 6 ] f[2,6] f[2,6]包含60,61,63,65,66,67,68,69
  • 预处理f数组如下
inline void init(){
	f[0][0]=1;
	for(int i=1;i<=10;i++){
		for(int j=0;j<10;j++){
			if(j==4) continue;
			for(int k=0;k<10;k++){
				if(j==6&&k==2) continue;
				f[i][j]+=f[i-1][k];
			}
		}
	}
}
  • 接着,考虑如何统计 [ l , r ] [l,r] [l,r]中符合条件的数的个数
  • 显然满足可减性,即 a n s [ l , r ] = a n s [ 0 , r ] − a n s [ 0 , l − 1 ] ans[l,r]=ans[0,r]-ans[0,l-1] ans[l,r]=ans[0,r]ans[0,l1]
  • 问题转为求 a n s [ 0 , n ] ans[0,n] ans[0,n],从高到低枚举哪一位比n小,例如对于n=3456
    显然[0,n]包含所有以0,1,2,3开头的四位数,则 a n s [ 0 , 3456 ] + = f [ 4 ] [ 0 ] + f [ 4 ] [ 1 ] + f [ 4 ] [ 2 ] ans[0,3456]+=f[4][0]+f[4][1]+f[4][2] ans[0,3456]+=f[4][0]+f[4][1]+f[4][2]
    同理对于3开头的四位数,答案包括所有以0,1,2,3开头的三位数…
    以此类推即可
  • CODE
#include<bits/stdc++.h>
using namespace std;
int f[11][11],L[11],len,l,r;
inline int read(){
	char c=getchar();
	int res=0;
	while(c<'0'||c>'9')c=getchar();
	while(c>='0'&&c<='9'){
		res=res*10+c-'0';
		c=getchar();
	}
	return res;
} 
inline void init(){
	f[0][0]=1;
	for(int i=1;i<=10;i++){
		for(int j=0;j<10;j++){
			if(j==4) continue;
			for(int k=0;k<10;k++){
				if(j==6&&k==2) continue;
				f[i][j]+=f[i-1][k];
			}
		}
	}
}
inline int solve(int n){
	len=0;
	while(n){
		L[++len]=n%10;
		n/=10;
	}
	L[len+1]=0;
	int ans=0,i=len;
	for(i=len;i>0;i--){
		for(int j=0;j<L[i];j++){
			if(j==4||(j==2&&L[i+1]==6)) continue;
			ans+=f[i][j];
		}
		if(L[i]==4||(L[i]==2&&L[i+1]==6)) break;
	}
	if(i==0) ans++;
	return ans;
}
int main(){
	memset(f,0,sizeof(f));
	init();
	scanf("%d%d",&l,&r);
	printf("%d",solve(r)-solve(l-1));
	return 0;
}

三、小结

  • 数位dp常见形式是:求在区间[l,r]中,有多少个数满足条件P(i)
  • 其中条件P(i)一般与数的大小无关,而与 数的组成 有关,常见的有以下几种:
  • 数i是递增/递减的:1234, 2579,…
  • 双峰的:19280,26193,…
  • 含/不含某一数字的,比如含49:49, 149, 1492,…
  • 被某一数m整除的,比如m=13:39,130,650
  • 【思路】采用记忆化搜索实现。
  • 搜索:dfs(i,j,k,ismax)枚举第i位的数,状态为j,前一位是k,是否达到上限(ismax=true/false)
  • 达到了上限只能统计cnt=sum(0num[i]),否则可以统计cnt=sum(09)
  • 记忆化:return ismax ? cnt : dp[i][j][k] = cnt
  • 搜索入口:dfs(len,0,0,true)
  • 记忆化搜索详解参见巨佬LJY的博客
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值