数位dp记录

文章讨论了在编程竞赛中遇到的数位动态规划问题,包括二维、三维的dp解决方案,如洛谷P3413SAC#1和CF55D/CF628D问题,展示了如何使用递归和优化技巧解决经典dp问题并避免内存溢出。
摘要由CSDN通过智能技术生成

学习了oiwiki的习题和数位dp,记录打卡一下。

都是比较经典的数位dp,感觉收益很大。

Ahoi2009 self 同类分布

经典的数位dp。

dp[20][200][200],开三维dp,第一维表示位数的遍历,第二维表示当前所有数字的位数之和,第三位表示当前数字的模数。通过暴力去枚举所有有可能的和(9*len)。然后一起进行数位dp,从而求解出最终得数。

// #pragma GCC optimize(3)  //O2优化开启
#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long long ll;
typedef pair<int,int> PII;
const int mod=1e9+7;
const int MX=0x3f3f3f3f3f3f3f3f; 
// static char buf[100000],*pa=buf,*pd=buf;
// #define gc pa==pd&&(pd=(pa=buf)+fread(buf,1,100000,stdin),pa==pd)?EOF:*pa++
// inline int read()
// {
//     register int x(0);register char c(gc);
//     while(c<'0'||c>'9')c=gc;
//     while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=gc;
//     return x;
// }
int l,r;
int dp[20][200][200];
int a[50];
int query(int x){
    if(x==0)return 0;
    int len=0;
    while(x)a[++len]=x%10,x/=10;
    auto dfs=[&](auto self,int id,int f,int sum,int p,int sux)->int{
        if(id==0){
            return sum==p&&(sux==0);
        }
        if(f==0&&dp[id][sum][sux]!=-1)return dp[id][sum][sux];
        if(sum>p)return 0;
        int res=9;
        if(f)res=a[id];
        int cnt=0;
        for(int i=0;i<=res;i++){
            cnt+=self(self,id-1,f&(i==res),sum+i,p,(sux*10ll+i)%p);
        }
        return dp[id][sum][sux]=cnt;
    };    
    int ans=0;
    for(int i=1;i<=9*len;i++){
        memset(dp,-1,sizeof dp);
        ans+=dfs(dfs,len,1,0,i,0);
    }
    // cout<<ans<<"+++\n";
    return ans;

}
void icealsoheat(){
    cin>>l>>r;
    l--;
    cout<<query(r)-query(l);

}
signed main(){
    ios::sync_with_stdio(false);          //int128不能用快读!!!!!!
    cin.tie();
    cout.tie();
    int _yq;
    _yq=1;
    // cin>>_yq;
    while(_yq--){
        icealsoheat();
    }
}

洛谷 P3413 SAC#1 - 萌数

dp[1005][10][10][2]

我们需要记录当前位数的位置,的上一位以及上上位置的数据值,以及当前是否已经满足了对称的条件从而分别作为数位dp的四维,来进行数位dp。

// #pragma GCC optimize(3)  //O2优化开启
#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long long ll;
typedef pair<int,int> PII;
const int mod=1e9+7;
const int MX=0x3f3f3f3f3f3f3f3f; 
// static char buf[100000],*pa=buf,*pd=buf;
// #define gc pa==pd&&(pd=(pa=buf)+fread(buf,1,100000,stdin),pa==pd)?EOF:*pa++
// inline int read()
// {
//     register int x(0);register char c(gc);
//     while(c<'0'||c>'9')c=gc;
//     while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=gc;
//     return x;
// }
string l,r;
int dp[1005][11][11][3];
int a[10005];
int len;
int ui;
int query(string &x){
    // int len=0;
    len=0;
    for(int i=0;i<x.size();i++){
        a[++len]=x[i]-'0';
    }
    auto dfs=[&](auto self,int id,int f,int f0,int le,int lle,int p)->int{
        if(id>len){
            return p;
        }
        if(le!=-1&&!f&&dp[id][le][lle][p]!=-1)return dp[id][le][lle][p];
        int res=9;
        if(f)res=a[id];
        int cnt=0;
        for(int i=0;i<=res;i++){
            int yu=-1;
            if(f0|i)yu=i;
            cnt=(cnt+self(self,id+1,f&&(i==res),f0||(i!=0),yu,le,p||((i==le)&&f0)||((i==lle)&&f0)))%mod;
        }
        
        if(lle!=-1&&f0){
            dp[id][le][lle][p]=cnt;
        }
        return cnt;
    };
    

    memset(dp,-1,sizeof dp);

    int ans=dfs(dfs,1,1,0,-1,-1,0);

    return ans;

}
void icealsoheat(){
    cin>>l>>r;
    int an=0;
    for(int i=0;i<l.size();i++){
        if(i>0&&l[i]==l[i-1]){
            an=1;
        }
        if(i>1&&l[i]==l[i-2])an=1;
    }
    cout<<((query(r)-query(l)+an)%mod+mod)%mod;


}
signed main(){
    ios::sync_with_stdio(false);          //int128不能用快读!!!!!!
    cin.tie();
    cout.tie();
    int _yq;
    _yq=1;
    // cin>>_yq;
    while(_yq--){
        icealsoheat();
    }
}

CF55D Beautiful numbers

我觉得很有代表性的一道题。

最开始想过用vector和map去维护1到9的数位dp,但最后以失败告终。看了题解才发现,我们只需要处理出1到9的最小公倍数lcm就行了。

三维dp代表了位数,当前数取模2520的值,以及lcm。

此时位dp[18][2520][2020],依旧会爆内存,但是我们可以发现,lcm一共有的数字不超过50个,我们需要提前预处理一下,可以把第三维优化成50。

与此同时,我们也不需要每一次都清空。我之前的数位dp都是正序遍历,并且每一次把dp值手动清空。但是这道题告诉我数位dp还是逆序遍历的好,逆序遍历在这道题中是可以不用每次都清空dp的值的,能大大减少时间复杂度(不然会t)。

#pragma GCC optimize(3)  //O2优化开启
#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long long ll;
typedef pair<int,vector<int>> PII;
const int mod=2520;
// const int MX=0x3f3f3f3f3f3f3f3f; 
// static char buf[100000],*pa=buf,*pd=buf;
// #define gc pa==pd&&(pd=(pa=buf)+fread(buf,1,100000,stdin),pa==pd)?EOF:*pa++
// inline int read()
// {
//     register int x(0);register char c(gc);
//     while(c<'0'||c>'9')c=gc;
//     while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=gc;
//     return x;
// }
 
string l,r;
int dp[20][2525][50];
int hh[2525];
int a[20];
void yu(){
    int idx=0;
    for(int i=1;i<=2520;i++){
        if(2520%i==0){
            hh[i]=++idx;
        }
    }
}
 
int LCM(int a,int b){
    return a*b/__gcd(a,b);
}
 
int query(string &x){
    int len=0;
    for(int i=x.size()-1;i>=0;i--){
        a[++len]=x[i]-'0';
    }
    // memset(dp,-1,sizeof dp);
    auto dfs=[&](auto self,int id,int f,int sum,int lcm)->int{
        if(!id)return sum%lcm==0;
        if(dp[id][sum][hh[lcm]]!=-1&&f==0)return dp[id][sum][hh[lcm]];
        int res=9;
        if(f)res=a[id];
        int ans=0;
        for(int i=0;i<=res;i++){
            int u=i?LCM(lcm,i):lcm;
            ans+=self(self,id-1,f&&(i==res),(sum*10+i)%mod,u);
        }
 
        if(f==0)dp[id][sum][hh[lcm]]=ans;
        return ans;
 
    };
    int ans= dfs(dfs,len,1,0,1);
    // cout<<ans<<"+++\n";
    return ans;
}
void icealsoheat(){
    cin>>l>>r;
    int an=0;
    vector<int>p(10,0);
    vector<int>ff(10,0);
    for(int i=0;i<l.size();i++){
        int x=l[i]-'0';
        ff[x]=1;
        for(int j=1;j<=9;j++){
 
                p[j]=(p[j]*10+x)%j;
 
        }
    }
    an=1;
    for(int i=1;i<=9;i++){
        if(p[i]&&ff[i]){
            an=0;
            break;
        }
    }
 
    // cout<<(query(r)-query(l)+an)<<"\n";
    printf("%lld\n",(query(r)-query(l)+an));
    // cin>>l;
    // query(l);
 
 
}
signed main(){
    ios::sync_with_stdio(false);          //int128不能用快读!!!!!!
    cin.tie();
    cout.tie();
    int _yq;
    _yq=1;
    cin>>_yq;
    memset(dp,-1,sizeof dp);
    yu();
    while(_yq--){
        icealsoheat();
    }
}

CF628D Magic Numbers

和之前的题很像,直接上代码

#pragma GCC optimize(3)  //O2优化开启
#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long long ll;
typedef pair<int,int> PII;
const int mod=1e9+7;
const int MX=0x3f3f3f3f3f3f3f3f; 
// static char buf[100000],*pa=buf,*pd=buf;
// #define gc pa==pd&&(pd=(pa=buf)+fread(buf,1,100000,stdin),pa==pd)?EOF:*pa++
// inline int read()
// {
//     register int x(0);register char c(gc);
//     while(c<'0'||c>'9')c=gc;
//     while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=gc;
//     return x;
// }
int m,d;
string L,R;
int dp[2005][2005];
void icealsoheat(){
    cin>>m>>d;
    cin>>L>>R;
    L=' '+L;
    R=' '+R;
    int cnt=0;
    auto dfs=[&](auto self,int id,int f,string &s,int sum)->int{
        if(id>=s.size()){
            return sum==0;
        }
        if(f==0&&dp[id][sum]!=-1)return dp[id][sum];
        int res=9;
        if(f)res=s[id]-'0';
        int bns=0;
        for(int i=0;i<=res;i++){
            if(i==d&&(id&1))continue;
            if(i!=d&&!(id&1))continue;
            int sux=(sum*10+i)%m;
            bns=(bns+self(self,id+1,f&(i==res),s,sux))%mod;
        }
 
        return dp[id][sum]=bns;
    };
 
    int ans=0;
    memset(dp,-1,sizeof dp);
    ans=dfs(dfs,1,1,R,0);
    // cout<<ans<<"++++\n";
    memset(dp,-1,sizeof dp);
    ans=(ans-dfs(dfs,1,1,L,0))%mod+mod;
    ans%=mod;
    // cout<<ans;
    bool ff=0;
    int summ=0;
    for(int i=1;i<L.size();i++){
        summ*=10;
        summ+=L[i]-'0';
        summ%=m;
        if(L[i]-'0'!=d&&!(i&1)){
            ff=1;
        }
        else if((i&1)&&L[i]-'0'==d)ff=1;
    }
    if(!ff&&summ==0)ans++;
    ans%=mod;
    cout<<ans;
 
}
signed main(){
    ios::sync_with_stdio(false);          //int128不能用快读!!!!!!
    cin.tie();
    cout.tie();
    int _yq;
    _yq=1;
    // cin>>_yq;
    while(_yq--){
        icealsoheat();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值