数位dp

1.不要62:

链接:http://acm.hdu.edu.cn/showproblem.php?pid=2089

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[20];
ll dp[20][10];//位,前一位的值

ll dfs(ll pos,ll pre,bool limit)
{
    if(pos == -1) return 1;
    if(!limit && dp[pos][pre] != -1) return dp[pos][pre];
    ll up = limit ? a[pos] : 9;
    ll tmp = 0;
    for(ll i = 0;i <= up;i++)
    {
        if((pre==6&&i==2)||(i==4))//i不可以等于4,当一位等于6时,该位不能为2
            continue;
        tmp += dfs(pos-1,i,limit && i == a[pos]);
    }
    if(!limit) dp[pos][pre] = tmp;
    return tmp;
}

ll solve(ll x)
{
    ll pos = 0;
    while(x)
    {
        a[pos++] = x%10;
        x /= 10;
    }
    return dfs(pos-1,0,true);
}

int main()
{
    ll le,ri;
    memset(dp,-1,sizeof dp);
    while(~scanf("%lld%lld",&le,&ri) && le + ri)
    {
        printf("%lld\n",solve(ri) - solve(le - 1));
    }
    return 0;
}

2.含有49

链接:http://acm.hdu.edu.cn/showproblem.php?pid=3555

ac:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;
typedef long long ll;

ll a[20];
ll dp[20][11];

ll dfs(ll pos,ll pre,bool limit)
{
    if(pos == -1) return 1;
    if(!limit && dp[pos][pre] != -1) return dp[pos][pre];
    ll up = limit ? a[pos] : 9;
    ll tmp = 0;
    for(ll i = 0;i <= up;i++)
    {
        if(pre == 4 && i == 9)continue;
        tmp += dfs(pos-1,i,limit && i == a[pos]);
    }
    if(!limit) dp[pos][pre] = tmp;
    return tmp;
}

ll solve(ll x)
{
    ll pos = 0;
    ll t=x;
    while(x)//把数位都分解出来
    {
        a[pos++] = x%10;
        x /= 10;
    }
    return t-dfs(pos-1,0,true);
}

int main()
{
    ll n;
    int t;
    scanf("%d",&t);
    memset(dp,-1,sizeof(dp));
    while(t--)
    {
        scanf("%lld",&n);
        printf("%lld\n",solve(n)-solve(0));
    }
    return 0;
}

链接:https://ac.nowcoder.com/acm/contest/221/G

定义一个序列a:7,77,777......,7777777(数字全为7的正整数,且长度可以无限大)
clearlove7需要从含有7的意志的数里获得力量,如果一个整数能被序列a中的任意一个数字整除,并且其数位之和为序列a中任意一个数字的倍数,那么这个数字就含有7的意志,现在给你一个范围[n,m],问这个范围里有多少个数字含有7的意志。

解析:

要求各位相加和是是7的倍数,该数是7的倍数

ac:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[20];
ll dp[20][10][10];//位,和,数位和

ll dfs(ll pos,ll sum,ll add,bool limit)//位,和,数位和
{
    if(pos == -1){
            return sum%7==0&&add%7==0;//判断是否add%7,sum%7都为0,注意0也会被算进去,但在这题里,是从1开始的
    }
    if(!limit && dp[pos][sum][add] != -1) return dp[pos][sum][add];
    ll up = limit ? a[pos] : 9;
    ll tmp = 0;
    for(ll i = 0;i <= up;i++)
    {
        tmp += dfs(pos-1,(sum*10+i)%7,(add+i)%7,limit && i == a[pos]);
    }
    if(!limit) dp[pos][sum][add] = tmp;
    return tmp;
}

ll solve(ll x)
{
    ll pos = 0;
    while(x)
    {
        a[pos++] = x%10;
        x /= 10;
    }
    return dfs(pos-1,0,0,true);
}

int main()
{
    ll le,ri;
    memset(dp,-1,sizeof dp);
    while(~scanf("%lld%lld",&le,&ri) && le + ri)
    {
        printf("%lld\n",solve(ri) - solve(le - 1));
    }
    return 0;
}

B-number:http://acm.hdu.edu.cn/showproblem.php?pid=3652

求1~n中数位包含13,%13==0的数的数目

数目dp,用sign标记上一位是否为1,前面是否有13等

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[25];
ll dp[25][15][3];//位,和,sign

ll dfs(ll pos,ll sum,int sign,bool limit)
{
    if(pos == -1){
            return sum%13==0&&sign==2;
    }
    if(!limit && dp[pos][sum][sign] != -1) return dp[pos][sum][sign];
    ll up = limit ? a[pos] : 9;
    ll tmp = 0;
    for(ll i = 0;i <= up;i++)
    {
        int csign=sign;
        if(sign==0&&i==1)
            csign=1;
        else if(sign==1){
            if(i==3)
                csign=2;
            else if(i!=1)
                csign=0;
        }
        tmp += dfs(pos-1,(sum*10+i)%13,csign,limit && i == a[pos]);
    }
    if(!limit) dp[pos][sum][sign] = tmp;
    return tmp;
}

ll solve(ll x)
{
    ll pos = 0;
    while(x)
    {
        a[pos++] = x%10;
        x /= 10;
    }
    return dfs(pos-1,0,0,true);
}

int main()
{
    ll n;
    memset(dp,-1,sizeof dp);
    while(~scanf("%lld",&n))
    {
        printf("%lld\n",solve(n));
    }
    return 0;
}

链接:https://ac.nowcoder.com/acm/contest/190/D

解析:

基础的数位dp,这里要r是多少,我们二分r,计算(1,r)的数目,求最小的r使得(1,r)的个数为n

ac:

#include<bits/stdc++.h>
#define ll long long
using namespace std;

ll n,dp[25][20][2];
int a[25];

ll dfs(ll pos,ll sum,ll sign,ll limit)
{
    if(pos==-1)
        return sign==1||sum%7==0;
    if(!limit&&dp[pos][sum][sign]!=-1)
        return dp[pos][sum][sign];
    ll ans=0;
    int en=limit?a[pos]:9;
    for(int i=0;i<=en;i++)
    {
        int csign=sign;
        if(i==7)
            csign=1;
        ans+=dfs(pos-1,(sum*10+i)%7,csign,limit&&i==en);
    }
    if(!limit)
        dp[pos][sum][sign]=ans;
    return ans;
}

ll solve(ll x)
{
    int pos=0;
    while(x)
    {
        a[pos++]=x%10;
        x/=10;
    }
    ll ans=dfs(pos-1,0,0,1);
    return ans;
}

int main()
{
    ll n,m;
    memset(dp,-1,sizeof(dp));
    scanf("%lld%lld",&m,&n);
    ll l=n,r=1e18;
    ll cc=solve(m);
    ll ans=1e18;
    while(l<=r)
    {
        ll mid=(l+r)/2;
        ll dd=solve(mid)-cc;
        if(dd<n){
            l=mid+1;
        }
        else{
            r=mid-1;
            ans=min(ans,mid);
        }
    }
    printf("%lld\n",ans);
    return 0;
}

https://vjudge.net/problem/CodeForces-55D

题意:

给两个数a,b

求a到b之间的能整除该数各位的数的个数

如果一个数可以整除他的各位数的最小公倍数,呢么就能整除各位数

dp[][][]代表:位,值,lcm,lcm可能非常大,呢么需要20*2520*2520空间,

但是lcm的种类非常少,1~9任意组合的lcm总共就48种.所以不需要开2520空间

值的话只要%2520,lcm可以离散化操作

ac:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[22];
int hash1[2522];
ll dp[22][2522][50];//位,和,lcm,本来至少要开20*2520*2520空间,但是lcm只有48种
ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b); }

ll dfs(int pos,int sum,int lcm,int limit)//数位,和,各数位最小公倍数
{
    if(pos==-1)return sum%lcm==0;//如果和能整除个数位的最小公倍数
    if(!limit&&dp[pos][sum][hash1[lcm]]!=-1)
        return dp[pos][sum][hash1[lcm]];
    ll ans=0;
    int en=limit?a[pos]:9;
    for(int i=0;i<=en;i++)
    {
        ans+=dfs(pos-1,(sum*10+i)%2520,i==0?lcm:lcm*i/gcd(lcm,i),limit&&i==en);//注意gcd(lcm,i)可能为0,直接/可能会崩溃
    }
    if(!limit)
        dp[pos][sum][hash1[lcm]]=ans;
    return ans;
}

void init()//离散化
{
    int num=0;
    for(int i=1;i<=2520;i++){//为2520的因子,这些数是1~9任意组合的最小公倍数的所以可能
        if(2520%i==0)
            hash1[i]=num++;
    }
}

ll solve(ll x)
{
    int pos=0;
    while(x){
        a[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,0,1,1);
}

int main()
{
    init();
    ll n,m,t;
    memset(dp,-1,sizeof(dp));
    scanf("%I64d",&t);
    while(t--)
    {
        scanf("%I64d%I64d",&n,&m);
        printf("%I64d\n",solve(m)-solve(n-1));
    }
    return 0;
}

http://www.51nod.com/Challenge/Problem.html#problemId=1009

题意:

给定一个十进制正整数N,写下从1开始,到N的所有正数,计算出其中出现所有1的个数.

例如:n = 12,包含了5个1。1,10,12共包含3个1,11包含2个1,总共5个1

解析:

简单数位dp

sum记录1的数目,返回sum

ac:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;
typedef long long ll;
 
ll a[20];
ll dp[20][20];
 
//状态:dp[pos][sum]:位,1的数目
ll dfs(ll pos,ll sum,bool limit)
{
    if(pos == -1) return sum;
    if(!limit && dp[pos][sum] != -1) return dp[pos][sum];
    ll up = limit ? a[pos] : 9;
    ll tmp = 0;
    for(ll i = 0;i <= up;i++)
    {
        tmp += dfs(pos - 1,sum+(i==1),limit && i == a[pos]);
    }
    if(!limit) dp[pos][sum] = tmp;
    return tmp;
}
 
ll solve(ll x)
{
    ll pos = 0;
    ll c=x;
    while(x)
    {
        a[pos++] = x%10;
        x /= 10;
    }
    return dfs(pos-1,0,1);
}
 
int main()
{
    ll ri;
    memset(dp,-1,sizeof dp);
    cin>>ri;
    cout<<solve(ri)<<endl;
    return 0;
}

https://ac.nowcoder.com/acm/contest/163/J

题意:

求1~n中有多少个数%(该数的各数位和)==0

解析:

数位dp+暴力

这里我开始4维,开3维也能计算,但是复杂度大10倍

数位dp的核心就是记忆化搜索,极大的节省复杂度

保存smod容易(累加就可以),条件是sum%smod==0,sum就无法开呢么大的数组,sum肯定要%mod

数的长度为12,mod最多有12*9种情况,这样sum就可以%mod了

当smod==mod&&sum%mod时就返回1

ac:

#include<bits/stdc++.h>
#define ll long long
using namespace std;

int a[20];
ll dp[14][120][120][120];//多加一维mod,时间复杂度降低10倍
ll dfs(int len,int mod,int smod,int sum,bool limit) //目前枚举的位数,mod,各位和,取余mod的余数,限制
{
    if(len==-1)
        return smod==mod && sum==0;//各位和相同,且刚好能除尽
    if(smod+(len+1)*9<mod)//复杂度降低40%
        return 0;
    if(!limit && dp[len][mod][smod][sum]!=-1)
        return dp[len][mod][smod][sum];
    int up=limit?a[len]:9;
    ll cur=0;
    for(int i=0;i<=up;i++)
    {
        if(i+smod>mod)//如果和已经大于的枚举mod,则不可能,复杂度降低20%
            break;
        cur+=dfs(len-1,mod,smod+i,(sum*10+i)%mod,limit && i==a[len]);//当这位有限制且已枚举到上限则下一位有限制
    }
    return limit?cur:dp[len][mod][smod][sum]=cur;
}

ll solve(ll x)
{
    memset(a,0,sizeof(a));
    int k=0;
    while(x){
        a[k++]=x%10;
        x/=10;
    }
    ll ans=0;
    for(int i=1;i<=9*k;i++) //暴力枚举每一种mod的可能,mod最多为位数*9,mod=(1~k*9),情况也不多
    {                       //如果不保存数位,呢么每次mod变化都要memset一次dp数组,且每次可用的记忆化搜索都很少
        ans+=dfs(k-1,i,0,0,true);
    }
    return ans;
}

int main()
{
    memset(dp,-1,sizeof(dp));
    int t;
    scanf("%d",&t);
    for(int cas=1;cas<=t;cas++)
    {
        ll n;
        scanf("%lld",&n);
        printf("Case %d: ",cas);
        printf("%lld\n",solve(n));
    }
    return 0;
}

http://acm.hdu.edu.cn/showproblem.php?pid=3709

解析:

一个数如果是平衡数,呢么呢个数的平衡点一定唯一

我们暴力枚举平衡点,

别忘了减去全为0的情况,00,0000,...等会被认为是多个数

ac:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll dp[20][20][2000];
int a[20];

ll dfs(ll pos,ll sign,ll sum,ll limit)//sign是平衡点位置
{
    if(pos==-1)
        return sum==0;
    if(sum<0)
        return 0;
    if(!limit&&dp[pos][sign][sum]!=-1)
        return dp[pos][sign][sum];
    ll ans=0;
    int en=limit?a[pos]:9;
    for(int i=0;i<=en;i++)
    {
        ans+=dfs(pos-1,sign,sum+(pos-sign)*i,limit&&i==en);
    }
    if(!limit)
        dp[pos][sign][sum]=ans;
    return ans;
}

ll solve(ll x)
{
    int cnt=0;
    while(x)
    {
        a[cnt++]=x%10;
        x/=10;
    }
    ll ans=0;
    for(int i=0;i<cnt;i++)//暴力枚举平衡点
        ans+=dfs(cnt-1,i,0,1);
    return ans-(cnt-1);//减去 00、000 、0000...的情况,它们其实都是0
}

int main()
{
    int t;
    ll a,b;
    memset(dp,-1,sizeof(dp));
    scanf("%d",&t);
    while(t--)
    {
        scanf("%lld%lld",&a,&b);
        printf("%lld\n",solve(b)-solve(a-1));
    }
    return 0;
}

https://ac.nowcoder.com/acm/contest/1168/I

题意:

1.这个整数在10进制下某一位是6.
2.这个整数在10进制下的数位和是6的倍数.
3.这个数是6的整数倍。
那么问题来了:邓志聪想知道在一定区间内与6无关的数的和。

解析:

现在枚举的某一位pos,我统计了这一位枚举i的满足条件的个数cnt,其实只要算i对总和的贡献就可以了,对于一个数而言第pos位是i,那么对求和贡献就是i*10^pos,就是十进制的权值,然后有cnt个数都满足第pos位是i,最后sum=cnt*i*10^pos.

ac:

#include<bits/stdc++.h>
#define mod 1000000007
#define ll long long
using namespace std;
ll a[20];
struct node
{
    ll cnt,sum;
}dp[20][10][10];//位,和,数位和
ll p[20];

void init()
{
    p[0]=1;
    for(int i=1;i<=18;i++)
        p[i]=(p[i-1]*10)%mod;
    for(int i=0;i<20;i++){
        for(int j=0;j<10;j++){
            for(int k=0;k<10;k++){
                dp[i][j][k].cnt=-1;
            }
        }
    }
}

node dfs(ll pos,ll sum,ll add,bool limit)//位,和,数位和
{
    if(pos == -1){
        return node{sum!=0&&add!=0,0};
    }
    if(!limit && dp[pos][sum][add].cnt!= -1) return dp[pos][sum][add];
    ll up = limit ? a[pos] : 9;
    node ans,tmp;
    ans.cnt=0,ans.sum=0;
    for(ll i = 0;i <= up;i++)
    {
        if(i==6)
            continue;
        tmp=dfs(pos-1,(sum*10+i)%6,(add+i)%6,limit && i == a[pos]);
        ans.cnt=(ans.cnt+tmp.cnt)%mod;
        ans.sum=ans.sum+(tmp.sum+((p[pos]*i)%mod)*(tmp.cnt%mod))%mod;
        ans.sum=ans.sum%mod;
    }
    if(!limit) dp[pos][sum][add]=ans;
    return ans;
}

ll solve(ll x)
{
    ll pos = 0;
    while(x)
    {
        a[pos++] = x%10;
        x /= 10;
    }
    return dfs(pos-1,0,0,true).sum;
}

int main()
{
    init();
    ll l,r;
    while(scanf("%lld%lld",&l,&r)!=EOF)
    {
        ll ans=(solve(r)-solve(l-1)+mod)%mod;
        printf("%lld\n",ans);
    }
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值