专题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';
    }
}
posted @ 2021-11-02 20:00  cyanine_告别  阅读(68)  评论(0编辑  收藏  举报