数位DP

你说得对,但是当我拿出 \(10^{18}\) 的数据时,阁下又该如何应对?


数位DP

数位DP是什么

让我们先看一道题:
给出一个区间 \(L\)~\(R\) ,求 \(L\)\(R\) 区间内每个数的数字和,如 \(123\) 这个数的数字和为\(1+2+3=6\)\((1≤L≤R≤10^2)\)
非常简单,非常暴力。
于是把 \(10^2\) 改为 \(10^{18}\) ……
非常抽象,非常毒瘤。前提是你不会数位DP
但是,当你学会了数位DP,你就可以获得 \(O(\log_{10}{N})\)的优秀时间复杂度
心动不如行动
数位DP就是来处理这种\(\color{red} 有着浓郁数学色彩\),并且\(\color{red}数据量抽象而巨大\)的题目的。

数位DP怎么做

数位DP常常需要 \(2\) 维或者更多维度。
其中第一位常为数字长度。
数位DP有两种实现形式:

  • 硬核递推(码量大,细节多,不推荐)
  • 记忆化搜索(强力推荐)

具体的说,就是在记忆化搜索的下,将数每一位都进行分支。有一个好康的格式如下:

long long dp[len][状态],b[10];
long long dfs(int pos,int state/*状态*/,bool is_max){
  if(pos==0)return 1;
  if(!is_max && ~dp[pos][state])return dp[pos][state];
  int end = is_max ? b[pos] : 9;
  long long ans=0;
  for(int i=0;i<=end;i++){
    if(满足某些条件)
      ans+=dfs(pos-1,state,is_max&&i==end);
  }
  if(!is_max)dp[pos][state] = ans;
  return ans;
}    
注意事项
  • 由于你用了数位DP,数据大小一定相当大,不开long long见祖宗。
  • 状态维度因题而异,自行添加
  • 在这种实现方法上,有时较难直接得到目标解,有时需要使用补集思想,求出来不符合条件的,或是删去前导零等等。

例题

P4999烦人的数学作业

题意

见上

给出一个区间 \(L\)~\(R\) ,求 \(L\)\(R\) 区间内每个数的数字和,如 \(123\) 这个数的数字和为\(1+2+3=6\)\((1≤L≤R≤10^{18})\)

裸的数位DP,只需添加一个数位和维,其值不超过 $9\times18=162$ 。
代码
#include<bits/stdc++.h>
#define MOD 1000000007
using namespace std;
int dp[20][189],b[20],pow10[20];
long long dfs(int pos,bool is_max,int f){
	if(pos==0)return dp[pos][f] = f;
	if(!is_max && dp[pos][f]!=-1)return dp[pos][f];
	int end = (is_max ? b[pos] : 9);
	long long ans=0;
	
	for(int i=0;i<=end;i++){
		if(1){
			ans= (ans+dfs(pos-1,is_max&&i==end,i+f)+MOD)%MOD;
		}
	}
	if(!is_max)dp[pos][f] = ans;
	return ans;
}
long long solve(long long x){
	int len=0;
	for(int i=0;i<=19;i++){
		b[i] = 0;
	}
	for(;x>0;x/=10){
		b[++len]=x%10;
	}
	return dfs(len,1,0);
}
long long T,l,r;
int main(){
	cin>>T;
	for(int i=0;i<19;i++){
		for(int j=0;j<=180;j++){
			dp[i][j] = -1;
		}
		
	}
	for(;T>0;T--){
		cin>>l>>r;
		cout<<(solve(r)-solve(l-1)+MOD)%MOD<<'\n';
	}
	return 0;
}

你说得对,但是T1用这个干嘛,能用纯数学用纯数学呗

posted @ 2025-07-19 12:54  ___jungle  阅读(14)  评论(0)    收藏  举报