2018 ACM上海大都会赛 J-Beautifule Number(数位DP应用)

本文介绍了一种使用数位动态规划(数位DP)解决特定数学问题的方法——寻找1到N之间能被其各数位之和整除的整数数量。文章详细解释了数位DP的概念,并提供了一个具体的实现案例,包括核心代码及其运行逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NIBGNAUK is an odd boy and his taste is strange as well. It seems to him that a positive integer number is beautiful if and only if it is divisible by the sum of its digits.

We will not argue with this and just count the quantity of beautiful numbers from 1 to N.

输入描述:

The first line of the input is T(1≤ T ≤ 100), which stands for the number of test cases you need to solve.
Each test case contains a line with a positive integer N (1 ≤ N ≤ 10^12).

输出描述:

For each test case, print the case number and the quantity of beautiful numbers in [1, N].

示例1

输入

2
10
18

输出

Case 1: 10
Case 2: 12

题目大意:给出一个数,要求找出1-n满足:本身能够整除自身各位数之和,的数字的数量。

思路:看数据范围就知道这不是一道简单的题,10^12,这么大,要么是有规律要么是有特殊的方法,,,emmm,这道题做法属于数位DP,但之前没有接触过,需要恶补一下。关于数位DP的两篇论文:算法合集之《浅谈数位类统计问题》算法合集之《数位计数问题解法研究》,这2篇文章讲的非常好,但是对于我这种资质愚钝的人来说可能还是有点朦胧,翻翻大佬们的博客,其中有一位大佬表示:其实数位DP(或者说所有记忆化搜索)都是可以看做通过搜索来填满状态的值。神来之笔,好不妙哉。

该题代码如下:

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
#include<string>
using namespace std;
typedef long long LL;
int a[20];//各位上的数 
LL dp[13][105][150];        //dp[i][j][k]记录 第i位,前面数位之和为j,对某个mod的余数是k的状态下满足条件的个数
int mod;
LL dfs(int p,int s,int r,bool l)//p为当前位数
{
    if(p==-1) return (s==mod&&!r);//枚举的数是合法的 
    if(dp[p][s][r]!=-1&&l==false) return dp[p][s][r];//限制条件 
    int up;
    if(l==true) up=a[p];
    else
        up=9;
    LL res=0;
    for(int i=0;i<=up;++i)//枚举不同的情况 
	{
        if(i+s>mod) break;         //剪枝
        res+=dfs(p-1,s+i,(r*10+i)%mod,l==true&&i==a[p]);
    }
    if(l==false) dp[p][s][r]=res;//记忆化 
    return res;
}
LL solve(LL N)
{
    int p=0;
    LL x=N;
    while(x>0)
	{
        a[p++]=x%10;
        x/=10;
    }//各位存储 
    LL s=0;
    for(int i=1;i<=9*p;++i)//枚举膜数 
	{
        mod=i;                          
        memset(dp,-1,sizeof(dp));       //这里的dp数组记录的只是针对一种模数的状态,所以每次都要清空
        s+=dfs(p-1,0,0,true);
    }
    return s;
}//数位DP 
int main()
{
    LL n;
    int k=0,t;
    scanf("%d",&t);
    while(t--)
	{
        scanf("%lld",&n);
        printf("Case %d: %lld\n",++k,solve(n));
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值