数位DP模板题

HDU-4507-恨7不成妻(区间内满足条件的所有数的平方和)
在这里插入图片描述

//考虑每个数对平方和的贡献 比如123^2=(100+23)*(100+23);(a+b)^2=a^2+b^2+2*a*b;
//所以 foru(i,0,up) 
//ans.cnt+=cur.cnt;
//ans.sum+=cur.sum+i*p[pos]*cur.cnt;
//ans.sqsum+=i*p[pos]*i*p[pos]*cur.cnt+cur.sqsum+2*i*p[pos]*cur.sum;
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define m(a,b) memset(a,b,sizeof a)
#define sd(a) scanf("%d",&a)
#define foru(i,a,b) for(int i=a;i<=b;i++)
#define pd(a) printf("%d\n",a)
#define pld(a) printf("%lld\n",a)
#define en '\n'
using namespace std;
typedef long long ll;
const int N=20+2,mod=1e9+7;
int a[N];ll p[N];
struct node
{
    ll cnt,sum,sqsum;
    node(){cnt=-1,sum=sqsum=0;}
}dp[N][7][7];
node num_dp(int pos,int k1,int k2,int limit)
{
    if(!pos)
    {
        node tmp;
        tmp.cnt=(k1&&k2);
        return tmp;
    }
    if(!limit&&dp[pos][k1][k2].cnt!=-1)
        return dp[pos][k1][k2];
    int up=limit?a[pos]:9;
    node ans;ans.cnt=0;
    for(int i=0;i<=up;i++)
    {
        if(i==7)
            continue;
        node cur=num_dp(pos-1,(k1*10+i)%7,(k2+i)%7,limit&&i==a[pos]);
        ans.cnt+=cur.cnt;ans.cnt%=mod;
        ans.sum+=(cur.sum+p[pos]*i%mod*cur.cnt%mod)%mod;ans.sum%=mod;
        ans.sqsum+=p[pos]*i%mod*p[pos]%mod*i%mod*cur.cnt%mod;
        ans.sqsum+=(cur.sqsum+2*p[pos]%mod*i%mod*cur.sum%mod)%mod;ans.sqsum%=mod;
    }
    if(!limit)
        dp[pos][k1][k2]=ans;
    return ans;
}
void init()
{
    p[1]=1;
    for(int i=2;i<N-2;i++)
        p[i]=(p[i-1]*10)%mod;
}
int main()
{
    #ifdef local
    freopen("input.txt","r",stdin);
    #endif
    init();
    ll x,y,tmp,ans1,ans2;
    int T;sd(T);
    while(T--)
    {
        scanf("%lld%lld",&x,&y);--x;
        if(x>y)
            tmp=x,x=y,y=tmp;
        int p=0;
        while(y)
            a[++p]=y%10,y/=10;
        node ti=num_dp(p,0,0,1);ans1=ti.sqsum;
        if(!x)
            ans2=0;
        else
        {
            p=0;
            while(x)
                a[++p]=x%10,x/=10;
            ti=num_dp(p,0,0,1),ans2=ti.sqsum;
        }
        pld(((ans1-ans2)%mod+mod)%mod);//T__T
    }
}

HDU-5179-beautiful number(前一位是后一位倍数且不为0)
在这里插入图片描述

//该题涉及到前导0,当高位前几位都是0000时,第pos位则不需要pre%i==0(因为此时pre==0)
//虽说1<=a[i]<=9,但foru(i,0,up) 要从0开始而不是1
//因为前几个高位可以为000,比1234 最后的数字难道不可以0093吗。
//但是这样会多枚举000000这个情况 最后应--ans 但是题目是要求区间内的ans2-ans1抵消了
//或者判断时改为if(lead&&(pos!=1||i)||i&&(pre%i==0))
//但是[x,y],--x之后x==0时num_dp直接返回1(代表数字0也是1种合理情况) 我们要杜绝他发生 
//所以if(p) ans1=num_dp()
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define m(a,b) memset(a,b,sizeof a)
#define sd(a) scanf("%d",&a)
#define pld(a) printf("%lld\n",a)
#define en '\n'
using namespace std;
typedef long long ll;
const int N=10+2;
int a[N];ll dp[N][N];
ll num_dp(int pos,int pre,int lead,int limit)
{
    if(!pos)
        return 1;
    if(!limit&&dp[pos][pre]!=-1)
        return dp[pos][pre];
    int up=limit?a[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++)
    {
        if(lead&&(pos!=1||i)||i&&(pre%i==0))
            ans+=num_dp(pos-1,i,lead&&(i==0),limit&&i==a[pos]);
    }
    if(!limit)
        dp[pos][pre]=ans;
    return ans;
}
int main()
{
    int T;sd(T);
    m(dp,-1);
    while(T--)
    {
        ll x,y,ans1=0,ans2=0;scanf("%lld%lld",&x,&y),--x;
        int p=0;
        while(x)
            a[++p]=x%10,x/=10;
        if(p)  //(不可以写x!!!我是傻逼啊啊 经过上述while(x),对x拆分已经使x变为了0)
            ans1=num_dp(p,-1,1,1);//防止x开始==0时,num_dp直接返回1
        p=0;
        while(y)
            a[++p]=y%10,y/=10;
        ans2=num_dp(p,-1,1,1);
        pld(ans2-ans1);
    }
}

HDU-4734-F(x)(区间内f(x)<=f[k]的个数)
思路:dp[pos][nexsum]代表剩下的pos个低位对f[x]的值的贡献应<=nexsum的这样的数的个数。
在这里插入图片描述

//由于多组数据要重复利用 
//dp[pos][nexsum]表示剩下的pos位对该数字大小的贡献应<=nexsum;
//如此边不需要memset 
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define m(a,b) memset(a,b,sizeof a)
#define sd(a) scanf("%d",&a)
#define pld(a) printf("%lld\n",a)
#define en '\n'
using namespace std;
typedef long long ll;
const int N=+92,Max=4599;
int a[N];
ll dp[N][Max+5],p[N];
ll num_dp(int pos,int pre,int nexsum,int limit)
{
    if(nexsum<0)
        return 0;
    if(!pos)
        return 1;
    if(!limit&&dp[pos][nexsum]!=-1)
        return dp[pos][nexsum];
    int up=limit?a[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++)
        ans+=num_dp(pos-1,i,nexsum-i*p[pos],limit&&i==a[pos]);
    if(!limit)
        dp[pos][nexsum]=ans;
    return ans;
}
int main()
{
    int T,cnt=0;sd(T);p[1]=1;
    for(int i=2;i<=9;i++)
        p[i]=p[i-1]*2;
    m(dp,-1);
    while(T--)
    {
        ll x,y;scanf("%lld%lld",&x,&y);
        ll KK=0;int k=1,p=0;
        while(x)
            KK+=x%10*k,x/=10,k*=2;
        while(y)
            a[++p]=y%10,y/=10;
        printf("Case #%d: %lld\n",++cnt,(num_dp(p,-1,KK,1)));
    }
}

HDU-3652-B-number(区间内数平衡数的个数)
在这里插入图片描述
题意:给出区间 [l,r] 内平衡数的个数,平衡数是指,以某位作为支点,此位左边的 数字乘以距离 的和与右边的相等.
思路:枚举支点位置 。
dp[pos][dex][sum],既然这是杠杆,就直接sum+=i*(pos-mid)
(i是该位选取的数,pos-mid是力臂有正有负)。if(!pos) return !sum;

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define m(a,b) memset(a,b,sizeof a)
#define sd(a) scanf("%d",&a)
#define pld(a) printf("%lld\n",a)
#define en '\n'
using namespace std;
typedef long long ll;
const int N=20,Max=17*18*9/2;
int a[N],mid;
ll dp[N][N][Max+5];
ll num_dp(int pos,int sum,int limit)
{
    if(sum<0) //剪枝(必须)
        return 0;
    if(!pos)
        return !sum;
    if(!limit&&dp[pos][mid][sum]!=-1)
        return dp[pos][mid][sum];
    int up=limit?a[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++)
        ans+=num_dp(pos-1,sum+i*(pos-mid),limit&&i==a[pos]);
    if(!limit)
        dp[pos][mid][sum]=ans;
    return ans;
}
int main()
{
    int T;sd(T);
    m(dp,-1);
    while(T--)
    {
        ll x,y,ans1=0,ans2=0;scanf("%lld%lld",&x,&y),--x;
        int p=0;
        while(x)
            a[++p]=x%10,x/=10;
        for(int i=1;i<=p;i++)
            mid=i,ans1+=num_dp(p,0,1);
        ans1-=p-1,p=0;//每一次枚举不同的支点 00000000(p位)都满足题意的 计算了p个0 实际上只需要1个 所以ans-=p-1;
        while(y)
            a[++p]=y%10,y/=10;
        for(int i=1;i<=p;i++)
            mid=i,ans2+=num_dp(p,0,1);
        ans2-=p-1;
        pld(ans2-ans1);
    }
}

HDU-3555-Bomb(0-n中数字序列存在49的个数)
在这里插入图片描述

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define m(a,b) memset(a,b,sizeof a)
#define sd(a) scanf("%d",&a)
#define pld(a) printf("%lld\n",a)
#define en '\n'
using namespace std;
typedef long long ll;
const int N=900+5,M=1e6+5;
int a[N];
ll dp[N][3];
ll num_dp(int pos,int sta,int limit)
{                                //sta=2:[pos+1,tot]存在49.
    if(!pos)                    //sta=1:[pos+1,tot]不存在49&&a[pos+1]==4.
        return sta==2;         //sta=0:[pos+1,tot]不存在49&&a[pos+1]!=4.
    if(!limit&&dp[pos][sta]!=-1)
        return dp[pos][sta];
    int up=limit?a[pos]:9,nsta;
    ll ans=0;
    for(int i=0;i<=up;i++)
    {
        if(sta==2)
            nsta=2;
        if(!sta)
            nsta=(i==4)?1:0;
        if(sta==1)
        {
            if(i==9)
                nsta=2;
            else if(i==4)
                nsta=1;
            else
                nsta=0;
        }
        ans+=num_dp(pos-1,nsta,limit&&i==a[pos]);
    }
    if(!limit)
        dp[pos][sta]=ans;
    return ans;
}
char s[100];
int main()
{
    #ifdef local
    freopen("input.txt","r",stdin);
    #endif
    int T;sd(T);
    while(T--)
    {
        m(dp,-1);
        scanf("%s",s);
        int pos=0,l=strlen(s);
        for(int i=l-1;i>=0;i--)//注意:这里将顺序颠倒了过来 num_dp的时候还相当于从高位向低位深搜(枚举)
            a[++pos]=s[i]-'0';//装的是个十百千万这种顺序
        pld(num_dp(pos,0,1));
    }
}

HDU-3652-B-number(找出1~n范围内含有13并且能被13整除的数字的个数)
在这里插入图片描述dp[pos][sta][mod] ,mod记录已经搜过的那几个高位%13的余数
nmod=(mod*10+i)%13;

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define m(a,b) memset(a,b,sizeof a)
#define sd(a) scanf("%d",&a)
#define pld(a) printf("%lld\n",a)
#define en '\n'
using namespace std;
typedef long long ll;
const int N=10+2,M=1e6+5;
int a[N];
ll dp[N][3][13];
ll num_dp(int pos,int sta,int k,int limit)
{
    if(!pos)
         return sta==2&&!k;
    if(!limit&&dp[pos][sta][k]!=-1)
        return dp[pos][sta][k];
    int up=limit?a[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++)
    {
        int nsta,tmp;
        if(sta==2)
            nsta=2;
        if(!sta)
            nsta=(i==1)?1:0;
        if(sta==1)
        {
            if(i==3)
                nsta=2;
            else if(i==1)
                nsta=1;
            else
                nsta=0;
        }
        tmp=(k*10+i)%13;
        ans+=num_dp(pos-1,nsta,tmp,limit&&i==a[pos]);
    }
    if(!limit)
        dp[pos][sta][k]=ans;
    return ans;
}
int main()
{
    #ifdef local
    freopen("input.txt","r",stdin);
    #endif
    m(dp,-1);
    int n;
    while(~sd(n))
    {
        int p=0;
        while(n)
            a[++p]=n%10,n/=10;
        pld(num_dp(p,0,0,1));
    }
}

猜你喜欢

转载自blog.csdn.net/qq_42576687/article/details/89648550