专题5 - 数位dp
数位dp
数位dp其实可以理解为,对于给出的数字,对每一位数字进行讨论并状态转移,这边先给出一道题说明一下数位dp的整个过程。
NC15035 送分了QAQ
数字中不能出现\(38\)和\(4\)。我们用\(f[i][j]\)表示到第\(i\)位,状态为\(j\)时有多少个讨厌的数。其中状态仅有三种,状态\(1\)表示上一个数为\(3\),状态\(2\)表示已经出现过\(4\)或者\(38\),其余为状态\(0\)。用\(dp(pos,st,flag)\)进行搜索,其中\(pos\)表示当前位置,\(st\)表示状态,\(flag\)表示数字选择是否受到限制(在前一位数字等于最大数时,当前位置的数字不能大于给出数字当前位置的数)。然后枚举当前位置的数字,当满足\((i==4\ ||\ st==2\ ||\ (st==1\ \&\&\ i==8)\),那么在\(2\)状态下进行搜索,如果\(i==3\),则转移至状态\(1\),否则转移至状态\(0\)。另外数字没有限制的情况必然会多次进行运算,所以在这里进行记忆化搜索。
搜索部分的代码其实是大多数常规数位dp的模板,后面的很多题目都会用到。
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fast ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int maxn = 10;
int f[maxn][3];
int a[maxn];
ll ans1=0,ans2=0;
int dp(int pos,int st,int flag)
{
if(pos==0) return st==2;
if(flag && f[pos][st]!=-1) return f[pos][st];
int x=flag?9:a[pos];
int ans=0;
for(int i=0;i<=x;i++)
{
if(i==4 || st==2 || (st==1 && i==8))
{
ans+=dp(pos-1,2,flag || i<x);
}
else if(i==3) ans+=dp(pos-1,1,flag || i<x);
else ans+=dp(pos-1,0,flag || i<x);
}
if(flag) f[pos][st]=ans;
return ans;
}
void init()
{
for(int i=1;i<=6;i++)
{
for(int j=0;j<3;j++)
{
f[i][j]=-1;
}
}
}
int main()
{
int n,m;
init();
while(cin>>n>>m)
{
if(n==0 && m==0) break;
n--;
int pos=0;
while(n)
{
a[++pos]=n%10;
n/=10;
}
ans1=dp(pos,0,0);
pos=0;
while(m)
{
a[++pos]=m%10;
m/=10;
}
ans2=dp(pos,0,0);
cout<<ans2-ans1<<'\n';
}
}
NC20665 7的意志
题目中提到数字需要被\(7,77,777,......,7777777\)整除,不难发现其实这些数都能被\(7\)整除,之后的数字都是多余的;其次,数字的总和也需要是\(7\)的倍数。那我们就在搜索的同时记录当前的数字的模数与数位和模数,套用模板即可。
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fast ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
int a[20];
ll f[20][10][10];
ll dp(int pos,int sum,int mod,int flag)
{
if(pos==0) return (sum%7==0 && mod%7==0);
if(flag && f[pos][sum][mod]!=-1) return f[pos][sum][mod];
int x=flag?9:a[pos];
ll res=0;
for(int i=0;i<=x;i++)
{
res+=dp(pos-1,(sum*10+i)%7,(mod+i)%7,flag || i<x);
}
if(flag) f[pos][sum][mod]=res;
return res;
}
ll cal(ll x)
{
int pos=0;
while(x)
{
a[++pos]=x%10;
x/=10;
}
ll res=dp(pos,0,0,0);
return res;
}
int main()
{
ll n,m;
memset(f,-1,sizeof(f));
while(cin>>n>>m)
{
if(n==0 && m==0) break;
cout<<cal(m)-cal(n-1)<<'\n';
}
}
NC17385 Beautiful Numbers
题目要求统计数字能被其数位和整除的数的个数。首先所有数字的数位和最多为\(108\),所以我们可以通过枚举数位和进行数位dp,同时统计数位和与模数即可。
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fast ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int maxn = 15;
ll f[maxn][110][110][110];
int a[maxn];
ll dp(int pos,int x,int mod,int sum,bool flag)
{
if(pos==0) return (x==sum && mod%sum==0);
if(flag && f[pos][x][mod][sum]!=-1) return f[pos][x][mod][sum];
ll d=flag?9:a[pos];
ll ans=0;
for(int i=0;i<=d;i++)
{
ans+=dp(pos-1,x,(mod*10+i)%x,sum+i,flag || i<d);
}
if(flag) f[pos][x][mod][sum]=ans;
return ans;
}
ll calc(ll x)
{
int pos=0;
while(x)
{
a[++pos]=x%10;
x/=10;
}
ll res=0;
for(int i=1;i<=108;i++)
{
res+=dp(pos,i,0,0,0);
}
return res;
}
int main()
{
fast;
memset(f,-1,sizeof(f));
int T;
cin>>T;
int cnt=0;
ll n;
while(T--)
{
cin>>n;
cout<<"Case "<<++cnt<<": "<<calc(n)<<'\n';
}
}
CF55D Beautiful Numbers
与上一题类似(连名字都一样),若能被数位的每一个数整除,一定能被数位的最小公倍数整除,所以我们可以枚举所有可能的公倍数,统计数字与\(2520\)(\(0-9\)所有数字的最小公倍数)的模数,最后判断是否能被整除即可。
#include<bits/stdc++.h>
#define fast ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define ll long long
#define pb push_back
using namespace std;
const int maxn = 100010;
ll dp[40][50][3000];
ll lcm(ll a,ll b)
{
return a/__gcd(a,b)*b;
}
map<int,int> mp;
vector<int> v;
ll l,r;
ll dfs(int pos,int sta,int mod,bool limit)
{
if(pos==-1) return mod%sta==0;
if(!limit && dp[pos][mp[sta]][mod]!=-1)
{
return dp[pos][mp[sta]][mod];
}
int end=limit?v[pos]:9;
ll ans=0;
for(int i=0;i<=end;i++)
{
if(i!=0)
{
ans+=dfs(pos-1,lcm(sta,i),(mod*10+i)%2520,limit&&i==end);
}
else
{
ans+=dfs(pos-1,sta,(mod*10+i)%2520,limit&&i==end);
}
}
if(!limit) dp[pos][mp[sta]][mod]=ans;
return ans;
}
ll solve(ll x)
{
v.clear();
while(x)
{
v.pb(x%10);
x/=10;
}
return dfs(v.size()-1,1,0,1);
}
int main()
{
fast;
int cnt=0;
for(int i=1;i<=2520;i++)
{
if(2520%i==0) mp[i]=cnt++;
}
memset(dp,-1,sizeof(dp));
int t;
cin>>t;
while(t--)
{
cin>>l>>r;
// cout<<solve(r)<<' '<<solve(l-1)<<'\n';
cout<<solve(r)-solve(l-1)<<'\n';
}
}