P4999

以下内容摘自OI-Wiki

引子

数位dp是指把一个数字按照个、十、百、千等等一位一位地拆开,关注它每一位上的数字。如果拆的是十进制数,那么每一位数字都是 0~9,其他进制可类比十进制。
数位 DP:用来解决一类特定问题,这种问题比较好辨认,一般具有这几个特征:

  1. 要求统计满足一定条件的数的数量(即,最终目的为计数);
  2. 这些条件经过转化后可以使用「数位」的思想去理解和判断;
  3. 输入会提供一个数字区间(有时也只提供上界)来作为统计的限制;
  4. 上界很大(比如 10^18 ),暴力枚举验证会超时。

数位 DP 的基本原理:

考虑人类计数的方式,最朴素的计数就是从小到大开始依次加一。但我们发现对于位数比较多的数,这样的过程中有许多重复的部分。例如,从 7000 数到 7999、从 8000 数到 8999、和从 9000 数到 9999 的过程非常相似,它们都是后三位从 000 变到 999,不一样的地方只有千位这一位,所以我们可以把这些过程归并起来,将这些过程中产生的计数答案也都存在一个通用的数组里。此数组根据题目具体要求设置状态,用递推或 DP 的方式进行状态转移。
数位 DP 中通常会利用常规计数问题技巧,比如把一个区间内的答案拆成两部分相减(即 \mathit{ans}{[l, r]} = \mathit{ans}-\mathit{ans}_{[0, l - 1]})(SadBee渲染)
那么有了通用答案数组,接下来就是统计答案。统计答案可以选择记忆化搜索,也可以选择循环迭代递推。为了不重不漏地统计所有不超过上限的答案,要从高到低枚举每一位,再考虑每一位都可以填哪些数字,最后利用通用答案数组统计答案。
接下来我们具体看几道题目。
https://www.luogu.com.cn/problem/P2602
https://acm.hdu.edu.cn/showproblem.php?pid=2089
https://loj.ac/problem/10165
https://www.spoj.com/problems/MYQ10/en/
https://www.luogu.com.cn/problem/P3311

回到正题

数位dp模板

#include<bits/stdc++.h>
using namespace std;
const long long maxn=1000100;
const long long mod=1e9+7;
long long t;
long long l,r;
long long a[maxn],num;
long long f[200][200];
long long dfs(long long x,long long sum,bool top) {
	if(!x) return sum;
	if(!top&&f[x][sum]>=0) return f[x][sum];
	long long bound=top?a[x]:9;
	long long ret=0;
	for(long long i=0; i<=bound; i++) ret=(ret+dfs(x-1,sum+i,top&&i==bound))%mod;
	if(!top) f[x][sum]=ret;
	return ret;
}
long long solve(long long x) {
	long long sum=0;
	while(x) {
		a[++sum]=x%10;
		x/=10;
	}
	return dfs(sum,0,1)%mod;
}
int main() {
	cin>>t;
	memset(f,-1,sizeof(f));
	while(t--) {
		cin>>l>>r;
		cout<<(solve(r)-solve(l-1)+mod)%mod<<'\n';
	}
	return 0;
}
posted @ 2024-09-19 18:57  yzc_is_SadBee  阅读(16)  评论(0编辑  收藏  举报