2018 ACM 国际大学生程序设计竞赛上海大都会赛——J Beautiful Numbers(数位DP)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kuronekonano/article/details/81664788

题目描述
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 ≤ 1012).
输出描述:
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

题意:输入一个数n,求出小于该数的,各数位加和能被其数值整除的值的数量。
如12—>1+2=3,而12可以被3整除。

对每个数字从高位开始向低位深搜,遍历所有数字出现的情况,这样明显是会超时的,但回溯的过程中记录下每种低位情况的数量,利用记忆化搜索可以记录之前搜到过的位数的确定数量。对于数位加和出的数字,利用同余模定理,每次都对一个余数取模,这样每次遍历一个余数,搜索时即可判断该数值各位加和后是否能整除余数,并且因为一直取模,实际数字并不会很大。

题目最大数值为1e12,那么即有11个9为最大值,各数位上的值加和为1~108区间内的数,枚举每种可能的余数,遍历过程中对取模余数为0的情况进行计数,即可求得答案。
注意在深搜过程中,假设已经搜索出了十位或百位0~9时的符合条件的数的数量,那么直接加和记忆化数组中的值即可,但当遍历到输入的值n时,不能加多,必须有一个遍历的上限,因此此处不能利用记忆化搜索直接加和,而是要一个个遍历没有记忆的部分是否符合条件。

#include<bits/stdc++.h>
#define LL long long
#define M(a,b) memset(a,b,sizeof a)
using namespace std;
LL n,dp[15][110][110][110],a[15];///一维记录数位长度,二维记录数位加和,三维记录实际数同余模,四维记录具体模数
LL dfs(int pos,int sum,int numod,bool limit,int calnum)
{
    if(pos==-1)return sum==calnum && !numod;///符合条件的的数判断,当数位加和等于模数,且实际数对其取模后等于0说明是beautiful number
    if(!limit && dp[pos][sum][numod][calnum]!=-1) return dp[pos][sum][numod][calnum];
    LL ans=0;
    int maxup = limit? a[pos]:9;///判断该遍历的位是否是上限,若是上限只能达到给出N的该位,否则可以遍历0~9
    for(int i=0;i<=maxup;i++)
    {
        if(sum+i>calnum)break;///加和超过遍历的取模数
        ans+=dfs(pos-1,sum+i,(numod*10+i)%calnum,limit && i==a[pos],calnum);
    }
    if(!limit) dp[pos][sum][numod][calnum]=ans;///如果不是上限值,此时计算的ans是有用的,能够被之后的计算直接整块调用的,记录到dp数组中
    return ans;
}
int main()
{
    int t,cas=0;
    M(dp,-1);
    scanf("%d",&t);
    while(t--)
    {
        int cnt=0;
        scanf("%lld",&n);
        while(n)///拆分各位
        {
            a[cnt++]=n%10;
            n/=10;
        }
        LL ans=0;
        for(int i=1;i<=108;i++) ans+=dfs(cnt-1,0,0,true,i);///遍历每一种数位和,用作同余模的模数,当累加实际数取模为0时是符合条件的
        printf("Case %d: %lld\n",++cas,ans);
    }
}

猜你喜欢

转载自blog.csdn.net/kuronekonano/article/details/81664788