数位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见祖宗。 - 状态维度因题而异,自行添加
- 在这种实现方法上,有时较难直接得到目标解,有时需要使用补集思想,求出来不符合条件的,或是删去前导零等等。
例题
题意
见上
给出一个区间 \(L\)~\(R\) ,求 \(L\) 到 \(R\) 区间内每个数的数字和,如 \(123\) 这个数的数字和为\(1+2+3=6\) 。\((1≤L≤R≤10^{18})\)
代码
#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用这个干嘛,能用纯数学用纯数学呗

浙公网安备 33010602011771号