我以前习惯叫"按位dp",貌似一样的.以前都是用记忆化搜索做,转移起来不用多想. 现在学了这个大牛 的写法, 感觉用迭代写也不错.
总结一下:
就是拿到一个上界bound.然后逻辑上将bound按位划分为三份,一份是统计过的,一份是当前统计位,最后一份是未统计位.
从bound的高到低位(a[n~1])进行统计,
统计 i 位时, a[n~i+1]都是统计过的, 都当成a[i](即那一位上最大可能的数码). 然后a[i]是当前统计位, 枚举 0~a[i]-1 这几个可能的数码. 而a[i-1~1]为未统计位, 每次对未统计位进行dp.(即在a[n~i]的限制下, 未统计位有多少种数字可能).
然后这道题, 思路都在代码里了.
代码:
#include<cstdio> #include<cstring> #include<iostream> #include<cmath> #include<string> #include<vector> #include<map> #include<algorithm> using namespace std; inline int Rint() { int x; scanf("%d", &x); return x; } inline int max(int x, int y) { return (x>y)? x: y; } inline int min(int x, int y) { return (x<y)? x: y; } #define FOR(i, a, b) for(int i=(a); i<=(b); i++) #define FORD(i,a,b) for(int i=(a);i>=(b);i--) #define REP(x) for(int i=0; i<(x); i++) typedef long long int64; #define INF (1<<30) const double eps = 1e-8; #define bug(s) cout<<#s<<"="<<s<<" " // [a, b]内 x%sigma(xi)=0 的个数. // 思路: // 首先区间最大可以为10^9,肯定要dp,归为子问题. // 那么上 数位统计dp. // 肯定有的两维, 数的位数 跟 数各位之和, 然后考虑转移, 再加上两维 模数 跟 余数. // 则状态为, d[位数][各位和][模数][余数] 表示满足的数的个数. // d[len+1][sum+x][mod][(res*10+x)%mod] = sigma( d[len][sum][mod][res] ). // 然后统计时, 从左到右逐位统计, 如321, // 第一位为0,1,2, 即 0xx, 1xx, 2xx. (xx表示任意两位数, 用dp出来的值确定符合的个数.) // 第二位为0,1, 即 30x, 31x. // 第三位为0,即 320(有点特殊, 转移到这个状态的是 d[0][0][mod][0], 而且还有一个数321不会被统计到, // 所以我们把最后一位单独进行统计). #define MAXN 9 #define MAXSUM (MAXN*9) //81 int tens[MAXN+2]; // tens[i] = 10^i. int d[MAXN+2][MAXSUM+2][MAXSUM+2][MAXSUM+2]; //d[位数][各位和][模数][余数] void dp() { memset(d, 0, sizeof(d)); //初始化边界, len=1 FOR(sum, 0, 9) FOR(mod, 1, MAXSUM) d[1][sum][mod][sum%mod]++; //dp FOR(len, 1, MAXN-1) //循环从1开始, 其实算的是 i+1 FOR(sum, 0, MAXSUM) FOR(mod, 1, MAXSUM) FOR(res, 0, MAXSUM-1) FOR(x, 0, 9) //枚举增量 { if(sum+x>MAXSUM) break; d[len+1][sum+x][mod][(res*10+x)%mod] += d[len][sum][mod][res]; } } int cal(int x) { if(x == 0) return 0; //特判 //处理成数组 int a[MAXN+3]; //1-th int n=0; int s = 0; //各位和 for(int t=x; t; t/=10) { a[++n] = t%10; s+=a[n]; } //统计 int cnt = 0; FOR(mod, 1, 9*n) //枚举可能的模数, 即最终各位和 { if(mod>MAXSUM) break; //x=10^9的时候可能会超 if(mod>x) break; //剪枝 int pre = 0; //前面的和 int sum = 0; //当前各位和 FORD(i, n, 2) //从高位到低枚举 { int len = i-1; //剩余位数 FOR(j, 0, a[i]-1) //枚举当前位的数码 { if(mod-sum-j<0) break; FOR(res, 0, mod-1) //枚举剩余部分可能的余数 { if((pre+j*tens[len]+res)%mod == 0) { //bug(len);bug(mod-sum-j);bug(mod);bug(res);bug(d[len][mod-sum-j][mod][res])<<endl; cnt += d[len][mod-sum-j][mod][res]; } } } sum += a[i]; if(sum>mod) break; pre += a[i]*tens[len]; } } //bug(cnt)<<endl; for(int t=x; ; t--, s--) //单独处理最低位 { if(t%s == 0) cnt++; if(t%10 == 0) break; //要借位了 } //bug(cnt)<<endl; return cnt; } void init() { tens[0] = 1; FOR(i, 1, MAXN) tens[i] = tens[i-1]*10; dp(); } int main() { init(); int T = Rint(); FOR(ca, 1, T) { int A = Rint(); int B = Rint(); printf("Case %d: %d\n", ca, cal(B)-cal(A-1)); } }