链接
http://www.lydsy.com/JudgeOnline/problem.php?id=1799
题解
这才是真正的题目啊(相对于前面两道)。
不难发现,9*18=162,就是说数字之和是很小的,那么套用NOIP2016 day2 T1的套路,加一维表示模数。
直接枚举模数p,然后统计1到N内有多少数各位之和为p且对p取模为0。
即f[i][j][k]表示i位数,和为j,对p取模余k的数字有多少种。(无限制)
g[i][j][k]同理,只不过加上了对数的大小的限制。
最蛋疼的地方在于转移,这道题把我教训了一顿。
一开始,直接用i-1的去转移i的,结果T的很惨。后来改成用i去转移i+1的,就AC了。
为啥呢?
举个栗子:假设N=20,枚举的p=10。那么在[1,20]内,各位数字之和为3的只有3、12,你的状态有f[1~2][0~3][0~9],实际有用的只有f[1][3][3]和f[2][3][2],其它的状态全都等于0,这样就造成了时间上的浪费,如果我们从开始去更新后面的,每次循环到一个值为0的就continue掉,能快很多。
其实这样分析完了,发现写一个记忆化搜索是最好的选择。(但是我比较懒,就不写了)
代码
//数位DP
#include <cstdio>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
ll N, f[20][200][200], num[20], g[20][200][200], mi[200], cnt;
void init(ll x)
{for(;x;x/=10)num[++N]=x%10;}
ll dp(ll p)
{
ll i, j, k, now;
memset(f,0,sizeof(f)), memset(g,0,sizeof(g));
for(i=0;i<=9;i++)f[1][i][i%p]=1;
for(i=0;i<=num[1];i++)g[1][i][i%p]=1;
for(i=1;i<N;i++)
for(j=0;j<=p;j++)
for(k=0;k<p;k++)
{
if(f[i][j][k]==0 and g[i][j][k]==0)continue;
for(now=0;now<=9;now++)
f[i+1][j+now][(k+now*mi[i])%p]+=f[i][j][k];
for(now=0;now<=num[i+1];now++)
if(now!=num[i+1])g[i+1][j+now][(k+now*mi[i])%p]+=f[i][j][k];
else g[i+1][j+now][(k+now*mi[i])%p]+=g[i][j][k];
}
return g[N][p][0];
}
ll work(ll x)
{
ll i, ans=0;
N=0;
init(x);
for(i=1;i<=9*N;i++)ans+=dp(i);
return ans;
}
int main()
{
ll a, b;
mi[0]=1;
for(int i=1;i<=18;i++)mi[i]=mi[i-1]*10;
scanf("%lld%lld",&a,&b);
printf("%lld",work(b)-work(a-1));
return 0;
}