[AHOI2009]同类分布(数位dp)
给出两个数 \(a,b\),求出 \([a,b]\) 中各位数字之和能整除原数的数的个数。\(1\le a,b\le {10}^{18}\)。
看起来就很像数位 dp,但是不知道要加哪些状态。我们逐步考虑:\(len,lim\) 肯定要有。要求“整除原数”,每一位的和 \(sum\) 和原数 \(num\) 似乎不错。\(sum\) 最大不会超过 \(18\times 9=162\) ,问题是,\(num\) 是 \(10^{18}\) 量级的。我们不可能直接放在状态里。
不难想到,取模可以缩小 \(num\) 范围。模数是什么?最理想的当然是 \(sum\),如果是取模完是 \(0\) 就说明整除了。然而,在搜索过程里,\(sum\) 是变化的,模数应当是一个定值。怎么办?我们大可以枚举 \(sum\) 的所有可能结果作为模数。这不会对时间带来很大压力。
这道题从数位 dp 死板的框架下突破出来,设计了巧妙的问题,引发思考。
下面是数位 dp 主体部分:
LL dfs(ri len,ri lim,ri sum,register LL num){
if(len==0){
if(sum==0) return 0;
else return sum==mod&&num==0;
}
if(lim==0&&f[len][sum][num]!=-1){
return f[len][sum][num];
}
ri up=lim?a[len]:9;
register LL ans=0;
for(ri i=0;i<=up;++i){
ans+=dfs(len-1,lim&&i==up,sum+i,(10ll*num+i)%mod);
}
if(!lim) f[len][sum][num]=ans;
return ans;
}
下面是 calc()
函数,枚举了模数 \(mod\) :
inline LL calc(LL x){
ri len=0;
while(x){
a[++len]=x%10;
x/=10;
}
register LL ans=0;
for(mod=1;mod<=len*9;++mod){
memset(f,-1,sizeof f);
ans+=dfs(len,1,0,0);
}
return ans;
}