对于数位dp,很多人有着不同的板子,我也曾经是使用递推板子,后来发现那种方法太过于灵活,对预处理的思维要求很高,因此在这里讲解一下我这种dfs的板子的通法
首先最简单的板子是f[][][],表示处理到了前i位,前缀的状态是什么,以及是不是当前位还是受限于最高位
受限的意思是,比如12345,当我们处理到第5位是,前面四位是1234,这样我们最高只能到5,否则的话到9都是可以的
这是基本上每道题都要有的三个状态,对于这道题,显然我们还需要两个状态,一个表示前面的数模13的答案是多少,一个表示前面是否已经存在13的情况了
当递归的结束的时候,我们只需要判断模数是否为0且前面是否已经存在13这种情况即可。这题的前缀肯定是前面是哪个数
这样就能记忆化搜索了,本题不需要处理前导0,但是有一些题需要处理前导0,我们只需要再给一维状态表示是否还在前导0的数,之后在循环中特判一下就行
虽然状态看上去很多,但是很多都是2位,并不占多少空间
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #include<map> #include<string> #include<vector> using namespace std; typedef long long ll; const int N=2e5+5; int f[20][10][14][2][2]; string s; string r; int len; int sum=13; int dfs(int cur,int p1,int r,int m,int flag){ if(cur==len) return m&&(r==0); auto &x=f[cur][p1][r][m][flag]; if(x!=-1) return x; int v=9; if(flag) v=s[cur]-'0'; int ans=0; int i; for(i=0;i<=v;i++){ ans+=dfs(cur+1,i,(10*r+i)%sum,m||(i==3&&p1==1),flag&&(i==v)); } return x=ans; } int solve(string t){ s=t; len=t.size(); int i; memset(f,-1,sizeof f); return dfs(0,0,0,0,1); } int main(){ while(cin>>r){ int ans=0; ans=solve(r); cout<<ans<<endl; } return 0; }