数位DP

数位dp

引入

数位是指把一个数字按照个、十、百、千等等一位一位地拆开,关注它每一位上的数字。如果拆的是十进制数,那么每一位数字都是 0~9,其他进制可类比十进制。

解决的问题:

求出在给定区间\([A,B][A,B]\)内,符合条件\(f(i)\)的数\(i\)的个数。条件\(f(i)\)一般与数的大小无关,而与数的组成有关。

由于数是按位dp,数的大小对复杂度的影响很小。

之所以要引入数位的概念完全就是为了dp。数位dp的实质就是 换一种暴力枚举的方式,使得新的枚举方式满足dp的性质,然后记忆化。

数位DP其实是很灵活的,所以一定不要奢求一篇文章就会遍所有数位DP的题,这一篇只能是讲清楚一种情况,其他情况遇到再总结,在不断总结中慢慢体会这个思想,以后说不定就能达到一看到题目就能灵活运用的水平。(其实DP都是这样……)

一般具有的特征:

  1. 要求统计满足一定条件的数的数量(即,最终目的为计数);

  2. 这些条件经过转化后可以使用「数位」的思想去理解和判断;

  3. 输入会提供一个数字区间(有时也只提供上界)来作为统计的限制;

  4. 上界很大(比如 \(10^{18}\)),暴力枚举验证会超时。

模板

数位 DP 中通常会利用常规计数问题技巧,比如把一个区间内的答案拆成两部分相减(即:

\(ans[l,r]=ans[0,r]-ans[0,l-1]\)

有了通用答案数组,接下来就是统计答案。统计答案可以选择记忆化搜索,也可以选择循环迭代递推。为了不重不漏地统计所有不超过上限的答案,要从高到低枚举每一位,再考虑每一位都可以填哪些数字,最后利用通用答案数组统计答案。

例题

数字计数

\(f[i]\)为满足i位的数字出现的次数。

可以发现,每个数出现的次数是相同的。

找规律可得:

\(f[i]=f[i-1]* 10 +10^{i-1}\)

\(m[i]\)来存\(10^i\)

\(f[i]=f[i-1]* 10+ m[i-1]\)

处理代码:

il void solve(int x, int *ans){
	int tmp = x;
	int len = 0;
	while(x) {a[++len]=x%10;x/=10;}//把每一位分离出来 
	For(i,len,1) {//从高位向低位拆 
		rep(j,0,9) ans[j]+=f[i-1]*a[i]; 
		rep(j,0,a[i]-1) ans[j]+=m[i-1];
		tmp -= m[i-1]*a[i];ans[a[i]]+=tmp+1;
		ans[0]-=m[i-1];
	}
}
posted @ 2023-06-19 19:41  XYini  阅读(1)  评论(0)    收藏  举报