[BZOJ1799]-数位DP(奇怪的DP方式)

说在前面

谜一般的数位DP…


题目

BZOJ1799传送门

题面

给出a,b,求出[a,b]中各位数字之和能整除原数的数的个数。(就喜欢这种短小精悍的题面=w=)
a,b都在long long范围内


解法

这个数位DP不能像普通的那样做。
按照平常的思维,会定义dp[i][j][k]为当前扫到前i位,数位和为j,数字取模数位和后为k,跑一遍R的dp再跑一遍L-1的dp
然而跑一遍是不行的…因为前面的取模和后面的取模没有关系,之间是不能转移的….
所以这道题需要枚举数位和(也就是模数),对每个数位和都跑一次dfs,因为数字在long long范围内,数位和最大只会有9*18=162,是可过的。


自带大常数的代码

#include 
#include 
#include 
using namespace std ;

long long L , R , dp[20][165][165] ;
int w[20] , cnt ;

void div( long long x ){
    cnt = 1 ;
    while( x ){
        w[cnt] = x%10 ;
        x /= 10 ; cnt ++ ;
    } cnt -- ;
}

long long dfs( int dep , int sum , int am , int mmod , bool limit ){
    if( dep == 0 ) return am == 0 && sum == mmod ;
    if( sum > mmod ) return 0 ; 
    if( !limit && dp[dep][sum][am] != -1 ) return dp[dep][sum][am] ;
    int lim = ( limit ? w[dep] : 9 ) ;
    long long rt = 0 ;
    for( int i = 0 ; i <= lim ; i ++ )
        rt += dfs( dep - 1 , sum + i , ( am * 10 + i )%mmod , mmod , limit && i == lim ) ;
    if( !limit ) dp[dep][sum][am] = rt ;
    return rt ;
}

void solve(){
    long long ans = 0 ;
    div( R ) ;
    for( int i = 1 ; i <= 162 ; i ++ ){
        //printf( "now :: %d\n" , i ) ;
        memset( dp , -1 , sizeof( dp ) ) ;
        ans += dfs( cnt , 0 , 0 , i , true ) ;
    }
    div( L - 1 ) ;
    for( int i = 1 ; i <= 162 ; i ++ ){
        memset( dp , -1 , sizeof( dp ) ) ;
        ans -= dfs( cnt , 0 , 0 , i , true ) ;
    }
    printf( "%lld" , ans ) ;
}

int main(){
    scanf( "%lld%lld" , &L , &R ) ;
    solve() ;
}

你可能感兴趣的:(------dp------)