数位DP(数字和与倍数,uva 11361)

本文介绍了一种使用数位动态规划(Digital Dynamic Programming, DDP)解决UVA 11361问题的方法。作者指出,这类问题需要在大数范围内寻找满足特定条件的数字数量,由于暴力枚举不可行,因此采用数位DP进行记忆化搜索。状态定义为3维,分别代表数字位数、对k的余数和数字和对k的余数。通过状态转移方程进行计算,并注意到在k大于100时无解,从而优化空间。作者反思了自己的错误,强调了数学严谨性和特殊情况的处理在解题中的重要性。" 126598533,14834812,Spring Security学习4天速成指南,"['java', 'spring', 'servlet', '安全框架', '认证功能']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这道题拖了一整天,WA了无数发,感觉自己数位DP特别弱,其实可能就是因为自己数学比较弱,或者说在推导和代入繁杂的公式以及边界很粗糙的讨论上太不够严谨了,总是觉得大概对就对了,这在数学题上是非常致命的缺点,因为公式错一点点就直接WA,讨论一遗漏也直接WA。然而这道题真蛋疼,明明公式错了,然而样例甚至自己生成的大部分数据都能过。。。


数位DP还需要大量的练习,太不够熟练了。


然后是解题思路:

有一类题目,就是问你在[a,b]中有多少个满足条件的数。a,b很大,暴力枚举是不可能,只能通过记忆化搜索实现数位DP来求解。

基本思路就是设f(x)表示不超过x的非负整数中满足条件的个数。则原题目的答案等于f(b)-f(a-1)。

首先是状态的定义,既然是数位DP,那么第几位(有几位)必然是其中一维。

题目有两个要求,第一个是数本身是k的倍数,第二个是各个数字之和也是k的倍数。这两个要求无必然联系,必须要同时确定才能确定状态。

因此状态为3维,dp(d,m1,m2)表示第i为(有i位),对k求余为m1,各位数和对k求余为m2的数的个数。

那么就枚举x从0到9状态转移方程就是dp[d][m1][m2]=sum(dp[d-1][((m1-x)%k+k)%k][((m2-x*10^(d-1))%k+k)%k])。

表示这个状态是由第d位放x,然后考虑d-1位的情况数。x取0~9,求和就是这个状态的值。

一个技巧就是如果希望a%b的值取到正数,那么就取(a%b+b)%b。

然后答案就是从高位往低位枚举,从0往bit[i]枚举,注意每一轮枚举别超了,最后别漏了刚好等于最大值的情况。


最后还有一些值得注意的地方,一开始我开了一个数组用来记忆化搜索,然后k太大了,有1e4,位数只有10。所以dp[15][1e4+10][1e4+10]根本就开不下。然后就想会不会有很多状态用不到,就决定改用哈希表。最后错在了公式= =。

事实上转哈希表这个方法确实不错,但是有一个更加显然的优化方法,那就是k大于100的时候都是无解的啊,那么只用开dp[15][110][110]就够了,大于100的时候直接输出0就好啦。我只是无脑转哈希表,也没仔细想到底是哪些状态不会用到,以为是状态离散的。事实上不能到达的状态是大量且连续的,所以直接开小数组+特判就OK。以后要多注意这种特殊情况,以优化空间和时间。


还有就是数位dp要注意特判的位置以及0的特判。

最好写在f函数里。


代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

/////////////////////////////////////
//hash
const ll hashsize=10000;
const ll maxstate=1000000;
ll head[hashsize];
struct node
{
    ll d,m1,m2,val,next;
}Node[maxstate];
ll tot;
void init()
{
    tot=0;
    memset(head,-1,sizeof(head));
}
ll get(ll d,ll m1,ll m2)
{
    ll h=(d+m1+m2)%hashsize;
    for(ll i=head[h];i!=-1;i=Node[i].next)
        if(d==Node[i].d&&m1==Node[i].m1&&m2==Node[i].m2)
            return Node[i].val;
    return -1;
}
ll add(ll d,ll m1,ll m2,ll val)
{
    ll h=(d+m1+m2)%hashsize;
    Node[tot].d=d;
    Node[tot].m1=m1;
    Node[tot].m2=m2;
    Node[tot].val=val;
    Node[tot].next=head[h];
    head[h]=tot++;
    return val;
}
///////////////////////////////////

ll mp(ll x,ll n)
{
    ll ret=1;
    while(n)
    {
        if(n&1) ret*=x;
        x*=x;
        n>>=1;
    }
    return ret;
}

ll f(ll d,ll m1,ll m2,ll K)
{
    ll ans=get(d,m1,m2);
    if(ans!=-1) return ans;
    if(!d)
    {
        if(m1==0&&m2==0) return add(d,m1,m2,1);
        else return add(d,m1,m2,0);
    }
    ans=0;
    ll POW=mp(10,d-1);
    for(ll x=0;x<10;x++)
        ans+=f(d-1,((m1-x)%K+K)%K,((m2-x*POW)%K+K)%K,K);
    return add(d,m1,m2,ans);
}

ll g(ll X,ll K)
{
    if(!X||K>90) return 1;
    ll ans=0;
    ll len=0;
    ll bit[15];
    while(X)
    {
        bit[++len]=X%10;
        X/=10;
    }
    ll tou=0;
    ll sum=0;
    for(ll i=len;i;i--)
    {
        tou*=10;
        ll POW=mp(10,i-1);
        for(ll j=0;j<bit[i];j++)
        {
            ans+=f(i-1,((-sum)%K+K)%K,(((-tou-j)*POW)%K+K)%K,K);
            sum++;
        }
        tou+=bit[i];
    };
    ans+=f(0,((-sum)%K+K)%K,((-tou)%K+K)%K,K);
    return ans;
}

int main()
{
    ll T;
    scanf("%lld",&T);
    while(T--)
    {
        init();
        ll A,B,K;
        scanf("%lld %lld %lld",&A,&B,&K);
        printf("%lld\n",g(B,K)-g(A-1,K));
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值