專題5 - 數位dp
阿新 • • 發佈:2021-11-02
數位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)\)
搜尋部分的程式碼其實是大多數常規數位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';
}
}