CodeForces 55D. Beautiful numbers (思维+数位DP)

54 篇文章 0 订阅
9 篇文章 3 订阅



D. Beautiful numbers
time limit per test
4 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

Volodya 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 each of its nonzero digits. We will not argue with this and just count the quantity of beautiful numbers in given ranges.

Input

The first line of the input contains the number of cases t (1 ≤ t ≤ 10). Each of the next t lines contains two natural numbers li and ri (1 ≤ li ≤ ri ≤ 9 ·1018).

Please, do not use %lld specificator to read or write 64-bit integers in C++. It is preffered to use cin (also you may use %I64d).

Output

Output should contain t numbers — answers to the queries, one number per line — quantities of beautiful numbers in given intervals (from li to ri, inclusively).

Examples
input
1
1 9
output
9
input
1
12 15
output
2
题意:给你一个区间,要求你算出这个区间里的数能被它自身每一位非0位数整除的个数。

思路:

分析:

利用记忆化搜索把小于等于num 的数中的所有beautiful数都搜出来

怎么搜呢?还是按照数字“位”来搜。

现在问题是:

1.怎么判断beautiful数?

2.怎么保存状态?

显然,需要将beautiful数的性质用状态保存下来。

beautiful数需要整除它所有的非零位

那么它只需整除它所有位的最小公倍数即可

所以就保存前面的公倍数,跟余数就行,注意不能像之前那样一步一步的除,要把所有的位数的lcm求出来才行,因为要看整个数能否被lcm整除的,所以lcm只是记录,x%lcm有用的部分只有x%2520,因为lcm最大就是2520,所以最后的答案肯定比2520小,也只有这一块对x%lcm有用,所以就缩小了范围

数字1~9的最小公倍数为2520(设为mxlcm

考虑这样一种保存状态的方法:

dp[i][j][k]表示长度为 i,所有数位的lcmjmxlcmk的答案

那么需要开一个dp[20][2520][2520]的数组,类型是long long

这数组显然是开不下的,要想办法压缩

对这个数组的第二维,“所有数位的lcmj”,其实j的取值虽然可能达到2520

但是j实际的数最多只有50

(若一个数能整除它的所有的非零数位, 
    那么相当于它能整除个位数的最小公倍数。 
    因此记忆化搜索中的参数除了len(当前位)和up(是否达到上界), 
    有一个prelcm表示前面的数的最小公倍数, 
    判断这个数是否是Beautiful Numbers,还要有一个参数表示前面数, 
    但是这个数太大,需要缩小它的范围。 
    缩小前面组成的数的范围: 
    可以发现所有个位数的最小公倍数是2520,假设当前的Beautiful Numbers是x, 
    那么 x % lcm{dig[i]} = 0, 
    又 2520%lcm{dig[i]} = 0, 
    那么x%(2520%lcm{ dig[i] }) = 0,x范围由9*10^18变为2520。 )

于是可以考虑开一个hs数组,hs[j] = id;表示给所有数位的lcmj的编号为id

这样一个dp[20][50][2520]的数组保存状态,那么万事俱备可以搜索了。 //这里处理的相当棒


再理解下数位dp,数位dp是针对位数进行dp的一种算法,对每一位都要新声明变量记录他后面的状态,都是从这位数往后的状态,其实就是从最高位往后递归,递归到个位然后不断回溯。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxlcm = 2520;
long long dp[20][50][maxlcm]; //三个数组分别记录处理的位数, lcm 为 j 的数字, 对2520取余为k
int remd[maxlcm], bit[20]; //用于存%2520的余数;
ll gcd(ll a, ll b)
{
    return b == 0 ? a : gcd(b, a%b);
}
ll lcm(ll a, ll b)
{
    return a * (b/gcd(a, b));
}
ll dfs(int len, int plcm, int pnum, int limit)
{
    if(len < 1) return pnum % plcm == 0;
    if(!limit && dp[len][remd[plcm]][pnum] != -1) return dp[len][remd[plcm]][pnum];
    int last = limit ? bit[len] : 9;
    ll ans = 0;
    for(int i = 0; i <= last; i++)
    {    //取膜maxlcm是为了把x的范围从1e18缩小到2520;
        int tnum = (pnum * 10 + i)% maxlcm, tlcm = plcm;  //这种类型,一定要重新申请一个变量记录,不能直接改变传进来的变量。否则for后面的数字全乱了,跟for后面那个ans一样
        if(i) tlcm = lcm(plcm, i); //记录前面数位的公倍数
        ans += dfs(len-1, tlcm, tnum, i == last && limit);
    }
    if(!limit) dp[len][remd[plcm]][pnum] = ans;
    return ans;
}
ll cal(ll n)  //注意这里面的参数是ll类型
{
    int k = 0;
    while(n)
    {
        bit[++k] = n % 10;
        n /= 10;
    }
    return dfs(k, 1, 0, 1);
}
void init()
{
    int k = 0; memset(dp, -1, sizeof(dp)); //初始化一次就行
    for(int i = 1; i <= maxlcm; i++)
    {
        if(maxlcm%i == 0)
            remd[i] = k++;
    }
}
int main()
{
    int t;
    init();
    scanf("%d", &t);
    while(t--)
    {
        ll r, l;
        scanf("%I64d%I64d", &l, &r);
        printf("%I64d\n", cal(r) - cal(l-1));  //闭区间,要-1
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值