[AHOI2009]同类分布
题目大意:
问在区间[l,r]内的正整数中,有多少数能被其个位数字之和整除。
思路:
数位DP。
极端情况下,每一位都是9,所以各位数字之和不超过9*18。(为了方便这里用了9*19)
f[i][j][k][flag],表示DP到从左往右第i位时,各位数字之和为j,这个数字在模mod意义下为k。
flag表示是否为边界情况。
转移的时候枚举这一位上的数p。
设当前位是cur,则转移方程为:
f[i-1][j+p][(k*10+p)%mod][false]+=f[i][j][k][false];
f[i-1][j+p][(k*10+p)%mod][false]+=f[i][j][k][true];(p<cur)
f[i-1][j+p][(k*10+p)%mod][true]+=f[i][j][k][true];(p=cur)
观察发现f的第1维只会同时用到两层,所以可以滚动数组。
1 #include<cstdio> 2 #include<cctype> 3 #include<cstring> 4 typedef unsigned long long qword; 5 inline qword getint() { 6 register char ch; 7 while(!isdigit(ch=getchar())); 8 register qword x=ch^'0'; 9 while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0'); 10 return x; 11 } 12 const qword pow[]={1ull,10ull,100ull,1000ull,10000ull,100000ull,1000000ull,10000000ull,100000000ull,1000000000ull,10000000000ull,100000000000ull,1000000000000ull,10000000000000ull,100000000000000ull,1000000000000000ull,10000000000000000ull,100000000000000000ull,1000000000000000000ull,10000000000000000000ull}; 13 const int SUM=9*19; 14 qword f[2][SUM+1][SUM][2]; 15 inline qword calc(const qword &n) { 16 const int len=__builtin_log10(n)+1; 17 qword ret=0; 18 for(register int mod=1;mod<=9*len;mod++) { 19 memset(f[len&1],0,sizeof f[len&1]); 20 f[len&1][0][0][true]=1; 21 for(register int i=len;i;i--) { 22 memset(f[!(i&1)],0,sizeof f[!(i&1)]); 23 const int cur=n%pow[i]/pow[i-1]; 24 for(register int j=0;j<=mod;j++) { 25 for(register int k=0;k<mod;k++) { 26 for(register int p=0;p<10;p++) { 27 if(j+p>mod) break; 28 f[!(i&1)][j+p][((((k<<2)+k)<<1)+p)%mod][false]+=f[i&1][j][k][false]; 29 if(p<cur) f[!(i&1)][j+p][((((k<<2)+k)<<1)+p)%mod][false]+=f[i&1][j][k][true]; 30 if(p==cur) f[!(i&1)][j+p][((((k<<2)+k)<<1)+p)%mod][true]+=f[i&1][j][k][true]; 31 } 32 } 33 } 34 } 35 ret+=f[0][mod][0][false]+f[0][mod][0][true]; 36 } 37 return ret; 38 } 39 int main() { 40 const qword l=getint(),r=getint(); 41 printf("%llu\n",calc(r)-calc(l-1)); 42 return 0; 43 }