Codeforces 55D Beautiful numbers 数位dp

http://codeforces.com/problemset/problem/55/D

题意:

要你求在A到B区间内的所有beautiful数,beautifil数的定义是这个数能被它自身数位上的所有数整除。

思路:

很好的一道数位dp的题目,大致的想法是这样的:首先我们可以知道一个数要能被它的所有数位上的非零数整除,那么这个数就必须要能被这个数中出现过的所有数字的最小公倍数整除,这样才能确保被所有数位上的数整除,那么dp的时候我们就必须要记录出现过的数位的最小公倍数,可喜的是由2-9组成的最小公倍数一共就只有48个。但是还有一个问题,因为我们的数是逐位来确定 的,因此我们并不能知道组成这个数的所有数位的最小公倍数是多少,但是我们可以先模上最大的那个最小公倍数,最后再判断数位的最小公倍数和余数的关系就可以了。这样本题就可以解决了。


代码:

#include <stdio.h>
#include <string.h>
#include <set>
#include <algorithm>
#include <vector>
using namespace std;

typedef __int64 LL ;
const int Mod = 2520;
LL A , B  ;
int digit[20] , pos ;
set<int> s ;
vector<int> v ;
int pp[Mod + 10] ;
LL dp[20][50][Mod] ;
LL dd[20] ;

int gcd(int a, int b){
    while( b ){
        int c = a ;
        a = b ;
        b = c % b ;
    }
    return a ;
}

void dfs1(int num ,int res ){
    if(num == 10)  {
        s.insert( res ) ;return ;
    }
    dfs1( num+1 , res );
    dfs1( num+1 , res/gcd(res , num) * num ) ;
}

void init(){
    s.clear();v.clear();
    dfs1(2, 1) ;
    set<int>::iterator it = s.begin();
    int cnt = 0 ;
    for(  ; it!=s.end() ; it++){
        v.push_back( *it ) ;
        pp[ *it ] = cnt++ ;
    }
    dd[0] = 1 ;
    for(int i=1;i<20;i++){
        dd[i] = dd[i-1] * 10 % Mod ;
    }
}

void get_digit( LL n ){
    pos = 0 ;
    while( n ){
        digit[ ++pos ] = n % 10 ;
        n /= 10 ;
    }
}

LL dfs(int pos, int cnt , int j , int limit ){
    if( !limit && dp[pos][cnt][j] != -1 )   return dp[pos][cnt][j] ;
    if( pos == 0 ){
        int cc = v[ cnt ] ;
        return ( j % cc == 0 ) ;
    }
    int end = limit ? digit[pos] : 9 ;
    LL res = 0 ;
    res += dfs( pos-1 , cnt , j , limit&(end == 0) ) ;
    for(int i=1;i<=end;i++){
        int cc = v[ cnt ] / gcd( v[cnt] , i ) * i ;
        int jj = ( j + dd[pos-1] * i ) % Mod ;
        res += dfs( pos-1 , pp[cc]  ,jj , limit&(i==end) ) ;
    }
    if( !limit )    dp[pos][cnt][j] = res ;
    return res ;
}

LL cal(LL n){   // calculate the number of b_number ;
    if( n == 0 )    return 1;
    get_digit( n );
    memset( dp , -1, sizeof(dp) );
    return dfs( pos , 0 , 0 , 1 ) ;
}

int main(){
    init() ;
    int T ;scanf("%d",&T);
    while( T-- ){
        scanf("%I64d%I64d",&A,&B);
        printf("%I64d\n",cal(B) - cal(A-1));
    }
    return 0 ;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值