考虑枚举数位之和m。对于每个m,问题简化为有多少个数数位之和==m,且%m=0。设计状态f[v][s][r][0/1]表示从高到低填到第v位,当前和为s,%m=r,未填满/填满的方案数。
TLE的DP,初始状态为f[19][0][0][1]=1:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<ctime>
#define ll long long
using namespace std;
ll f[20][170][170][2],mi[20];
ll solve(ll n,int mod)
{
memset(f,0,sizeof(f));
f[19][0][0][1]=1;
for(register int i=19;i>=1;i--)
{
int t=n/mi[i-1]%10,dw=max(mod-9*i,0);
for(register int j=0;j<=9;j++)
for(register int s=dw;s<=mod-j;s++)
for(register int r=0;r<mod;r++)
{
int cs=s+j,cr=((r<<3)+(r<<1)+j)%mod;
f[i-1][cs][cr][0]+=f[i][s][r][0];
if(j<t) f[i-1][cs][cr][0]+=f[i][s][r][1];
else if(j==t) f[i-1][cs][cr][1]+=f[i][s][r][1];
}
}
return f[0][mod][0][0]+f[0][mod][0][1];
}
int main()
{
ll a,b,ans=0;
scanf("%lld%lld",&a,&b);
mi[0]=1;
for(int i=1;i<=18;i++) mi[i]=mi[i-1]*10;
for(int i=1;i<=162;i++)
ans+=solve(b,i)-solve(a-1,i);
printf("%lld\n",ans);
return 0;
}
AC的记忆化搜索,终止状态为f[0][m][0][0/1]=1(思路要反过来一下):
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
ll f[20][170][170][2];
int a[20],cnt=0,tot,mark[20][170][170][2];
ll dp(int v,int s,int r,int b)
{
if(v==0) return ((s==tot)&&(r==0)) ;
if(mark[v][s][r][b]==tot) return f[v][s][r][b];
mark[v][s][r][b]=tot;ll t=0;
int lx=max(0,tot-s-(v-1)*9),rx=min((b)?a[v]:9,tot-s);
for(int i=lx;i<=rx;i++) t+=dp(v-1,s+i,(r*10+i)%tot,b&(i>=a[v]));
return (f[v][s][r][b]=t);
}
ll solve(ll x)
{
memset(mark,0,sizeof(mark));
ll re=0;cnt=0;
for(;x;x/=10) a[++cnt]=x%10;
for(tot=1;tot<=cnt*9;tot++)
re+=dp(cnt,0,0,1);
return re;
}
int main()
{
ll x,y;
scanf("%lld%lld",&x,&y);
printf("%lld\n",solve(y)-solve(x-1));
return 0;
}