hdu 4722 Good Numbers(找规律,记忆化搜索,数位dp)

题目链接:

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

解题思路:

题目大意:

给你两个数a,b(a<=b),问你a~b满足各个数位相加能整除10的个数,含边界。

算法思想:

先枚举一下0~200内满足条件的值,0,19,28,37,46,55,64,73,82,91,109,118,127,136,145,154,163,172,181,190.规律很显然就出来了,0~10中有一个,10~20中有一个。。。可知:每十个中必有一个(自己可以在纸上画一下)。一个很大的数共有n位,前面n-1位的和是p而最后一位还没确定,最后一位可以取q=0~9,自己可以想一下结果肯定是(p+q)%10,而模上10以后的结果必是从0~9,所以每十个里面必有一个。接下来的就好办了,问你a~b满足条件的个数,就先求出边界c~d(c<=a且最大的满足条件的数,d<=b且最大的满足条件的数),最后结果就是(d-c)/10。

AC代码(找规律);

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

typedef long long ll;

bool judge(ll x){
    int sum = 0;
    while(x){
        sum += x%10;
        x /= 10;
    }
    if(sum % 10 == 0)
        return true;
    else
        return false;
}

int main(){
    int T,t = 1;
    scanf("%d",&T);
    while(T--){
        ll a,b;
        scanf("%lld%lld",&a,&b);
        a--;
        while(!judge(a))
            a--;
        while(!judge(b))
            b--;
        printf("Case #%d: %lld\n",t++,b/10-a/10);
    }
    return 0;
}

AC代码(记忆化搜索):

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

typedef long long ll;
const int maxn = 25;

ll dp[maxn][12];
ll digit[maxn];
ll a,b;

ll dfs(int pos,int pre,bool limit){//pre表示前面各位数字之和对该数取模的结果 
    if(pos == -1)
        return pre == 0;
    if(!limit && dp[pos][pre]!=-1)
        return dp[pos][pre];

    ll res = 0,tmp = limit?digit[pos]:9;

    for(int i = 0; i <= tmp; i++){
        int new_pre = (pre+i)%10;
        res += dfs(pos-1,new_pre,limit&&i==tmp);
    }
    if(!limit)
        dp[pos][pre] = res;
     return res;
}

ll solve(ll n)
{
     int len=0;
     while(n){
         digit[len++] = n%10;
         n /= 10;
     }
     return dfs(len-1,0,true);
}

int main(){
     int T;
     scanf("%d",&T);
     for(int i = 1; i <= T; i++){
         memset(dp,-1,sizeof(dp));
         scanf("%lld%lld",&a,&b);
         printf("Case #%d: %lld\n",i,solve(b)-solve(a-1));
     }
     return 0;
}


dp[i][j]表示到第i位,数字和%10为j,然后进行dp,注意完全匹配的情况是要+1,而其他情况是从0 到 9 都要考虑
AC代码(数位dp):

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;
ll a,b,dp[20][10];
ll v[20],vn;

void solve2(ll num){
    vn = 0;
    while(num){
        v[++vn] = num % 10;
        num /= 10;
    }
    for(int i = 1; i <= vn / 2; i++)
        swap(v[i], v[vn - i + 1]);
}

ll solve(ll num){
    if (num == -1)
    return 0;
    memset(dp, 0, sizeof(dp));
    solve2(num);
    int x = 0;
    for (int i = 1; i <= vn; i++){
        for (int j = 0; j < 10; j++){
            for (int k = 0; k < 10; k++){
                dp[i][(j + k) % 10] += dp[i - 1][j];
            }
        }
        for (int j = 0; j < v[i]; j++) {
            dp[i][(x + j) % 10]++;
        }
        x = (x + v[i]) % 10;
    }
    if (!x)
        dp[vn][0]++;
    return
        dp[vn][0];
}

int main(){
    int T,t = 1;
    scanf("%d", &T);
    while(T--){
        scanf("%lld%lld",&a,&b);
        printf("Case #%d: %lld\n",t++,solve(b)-solve(a-1));
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值