数位DP做题记录

                                                                               XDU1161(一本通1588)

思路:

用二维数组dp[i][j]表示数字取到i位的情况下,各位数字之和对题中所给N取模为j的数字个数。例如对于样例来说,dp[1][0]表示数字最多可取到1位,各个位之和对9取模为0个数字个数,这样的数字有两个,即0和9。

类似地,dp[1][1]=1,因为1%9=1;dp[1][2]=1,因为2%9=2;dp[2][0]=11,因为0,9,18,27,36,45,54,63,72,81,90,99满足条件。注意这里dp数字的dp[i][j],i是指数字的总位数可以是1,2,.....i,而不是只算位数是i的数字。

可以先预处理一下dp数组,假如模数为mod,先用一个for循环赋值dp[1][0]到dp[1][mod-1]

for(int i=0;i<=9;i++) dp[1][i%mod]+=1;

然后用i可以递推i+1的情况,如果已经知道dp[i][j]的值,现在将数字位数可取值增加到i+1,第i+1位上的数字可以取0到9,假如取的是y,由于之前i位之和取模后是j,加上这个y之后,总共的i+1位之和取模后应该是(j+y)%mod,所以dp[i+1][(j+y)%mod]+=dp[i][j]。

求取区间[a,b]之间符合条件的数字个数,可以当作求0到b之间符合的个数减去0到a-1之间符合条件的个数。

要求0到X中间符合条件的数字个数,先把X每一位存到数组shu[13]中,例如X=387,求0到387之间,对9取模符合要求的个数。

387被存储为 shu[1]=7,shu[2]=8,shu[3]=3;(这个顺序和数字读起来刚好相反了)。

假如要暴力枚举的算,可以发现,当数字的最高位取其最大值时,低位的取值是受限制的,最高位取2时,十位上最多只能取到8,但如果最高位取1或者2,后两位是任意取0到9之间。所以,对于最高位取值val<shu[3]时,这里就是1或者2,对答案的贡献就是dp[i-1][(mod-val%mod)%mod],这种写法是因为最高位取得了val,其余位置之和加起来,再和val之和取模mod为0时符合条件,例如最高位取2,剩余位置之和对mod取模为7时,再加上这个2,再对mod取模为0,符合条件。

所以最高位取1,或者2时,对答案贡献 dp[1][8]和dp[2][7]。

最高位取0时,低的两位也都是可以取0到9之间任意值的,这时对答案贡献dp[2][0],

最高位取3时,就开始出现限制,这时第二位只能取0到8。而取8时最低位也受到限制只能取0到7。这里再分一次情况:

第二位取0到7时,最低为可以任意取0到0,并且由于最高位对mod取模为3,其余各位之和需要对mod取模为6,所以第二位取0到7时,对答案贡献为dp[1][6],dp[1][5],dp[1][4],dp[1][3],dp[1][2],dp[1][1],dp[1][0],dp[1][8]。

第二位取8时,最低位可以取0到7,对于取到最低位的情况,可以直接把各个位置之和加起来,取模为0则答案加1。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll N,M,mod;
ll shu[13];
ll dp[13][110];
int cnt;

void init()
{
    memset(dp,0,sizeof(dp));
    for(ll i=0;i<=9;i++)
        dp[1][(i%mod)]+=1;
        
    for(ll i=2;i<=12;i++)
        for(ll j=0;j<=9;j++)
            for(ll k=0;k<mod;k++)
                dp[i][(j+k)%mod]+=dp[i-1][k];
}

void bian(ll n)
{
    cnt=0;
    while(n)
    {
        shu[++cnt]=n%10;
        n/=10;
    }
}

ll ac(ll n)
{
    if(n<10)
    {
        if(n%mod==0)return 1;
        else return 0;
    }
    
    bian(n);
    ll ans=0;

    ans+=dp[cnt-1][0];//这是最高位取0时,前cnt-1位可以任意取0到9之间

    for(ll i=1;i<shu[cnt];i++)//最高位取1到shu[cnt]之间,前cnt-1位也可任意取
    {
        ans+=dp[cnt-1][(mod-i%mod)%mod];
    }
    ll he=shu[cnt];

    for(ll i=cnt-1;i>=1;i--)
    {
        for(ll j=0;j<shu[i];j++)
        {
            ll need=(mod-(he+j)%mod)%mod;
            if(i>1)ans+=dp[i-1][need];
            else
            {
                if((he+j)%mod==0)ans++;
            }
        }
        he+=shu[i];
    }
    if(he%mod==0)ans++;
    return ans;
}

int main()
{
    while(scanf("%lld %lld %lld",&N,&M,&mod)!=EOF)
    {
        init();
        printf("%lld\n",ac(M)-ac(N-1));
    }
    return 0;
}

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值