1. 程式人生 > 其它 >專題5 - 數位dp

專題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';
    }
}