动态规划学习系列——数位DP(练手四)

时间:2022-12-15 22:47:14

题目链接:hihoCoder 1033

解题思路:
思路超级简单,就是单纯的数位DP,但是,还是觉得好恶心。需要特殊考虑的就是第一位数字为0的情况,不能直接把状态转移的结果拿出来。
状态:dp[i][j][k+100],i 位数中以 j 开头的数交叉和为k的数量
(这里因为k有可能取负数,所以需要让它变正,加100即可)

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MOD 1000000007
#define mid 100

using namespace std;

long long L,R,M,res[20][10][205],dp[20][10][205];
long long base[20];
int cnt,tmp[20];

void cal(long long num)
{
    cnt=0;
    while(num)
    {
        tmp[++cnt]=num%10;
        num/=10;
    }
}

long long solve(long long num)
{
    if(num==0)
        return 0;
    memset(dp,0,sizeof(dp));
    memset(res,0,sizeof(res));
    cal(num);
    for(int i=0;i<10;i++)
        res[1][i][i+mid]=i, dp[1][i][i+mid]=1;
    for(int i=2;i<=cnt;i++)
    {
        for(int j=0;j<10;j++)
        {
            for(int k=0;k<10;k++)
            {
                for(int l=0;l<200;l++)
                {
                    dp[i][j][j-l+2*mid] = (dp[i][j][j-l+2*mid] + dp[i-1][k][l])%MOD;
                    res[i][j][j-l+2*mid] = (res[i][j][j-l+2*mid] + res[i-1][k][l])%MOD;
                    res[i][j][j-l+2*mid] = (res[i][j][j-l+2*mid] + ((j*base[i])%MOD)*dp[i-1][k][l])%MOD;
                }
            }
        }
    }

    long long ans=0,val=0,sum=0;
    for(int i=1;i<cnt;i++)
    {
        for(int j=1;j<10;j++)
        {
            ans = (ans+res[i][j][M+mid])%MOD;
        }
    }
    for(int i=cnt;i>=1;i--)
    {
        for(int j=0;j<tmp[i];j++)
        {
            if(i==cnt&&j==0)
                continue;
            if(i%2==cnt%2)
                ans = (ans+(sum*dp[i][j][M-val+mid])%MOD+res[i][j][M-val+mid])%MOD;
            else
                ans = (ans+(sum*dp[i][j][val-M+mid])%MOD+res[i][j][val-M+mid])%MOD;
        }
        sum = ((base[i]*tmp[i])%MOD + sum)%MOD;
        if(i%2==cnt%2)
            val+=tmp[i];
        else
            val-=tmp[i];
    }
    return ans;
}

int main()
{
    base[1]=1;
    for(int i=2;i<=18;i++)
        base[i]=(10*base[i-1])%MOD;

    while(~scanf("%lld %lld %lld",&L,&R,&M))
    {
        printf("%lld\n",(solve(R+1) - solve(L) + MOD)%MOD);
    }

    return 0;
}

总结:
记住负数取模要先加模数!!!
千万记住0这种特殊情况!!!