编程之美这本书还是挺不错的,很多分析问题的方法都是让人受益的。算了废话什么的就不讲了。
如何求解区间[x,y]内数字1出现的次数? 看看数据我就泪奔了。。。yy了一下就直接看解答了。
这么这么长,烦人啊。还是自己想想吧!呵呵,果断会想数位DP啊。然后就写,debug。结束。
找了oj提交? wa? wa?肿么回事呢? 不会出错啊,写了个暴力的代码对拍,没有问题啊。正常数据,边界
数据都测试过了。No Problem。给问题板块发了分报告。。。继续检查。。。算了吃饭去了。。。。
回来又是debug,真心没错啊。。提交,编译中,测试中。。ac。
这是哪门子事啊。 当时就有一种很坑的感觉,浪费了我一下午啊。
我的解法:
对于区间[x,y]。结果是等于[0,y]-[0,x-1]。那么用什么办法把[0,z]区间内1的个数统计出来呢?
按位去dp,特殊考虑该为填1的时候。我用is[i][j][1]记录长度为i时第i位为j时区间内会出现数中包含1的数
的个数。额, 好像有点绕口。is[i][j][0]记录长度为i时第i位为j时区间内数中不含有1的数的个数。如果j为
1的话,is[i][j][0]=0,因为该位填1后,这样是不会有不合法的数的。dp[i][j]记录区间1的个数。
dp[i][j] += dp[i-1][k]; 如果 j=1, dp[i][j] += is[i-1][k][1]+is[i-1][k][0]; j=1是表示该为填1,
此时1xxxx都是合法额,所以都是要加上去的。
这题测试链接: jobdu oj http://ac.jobdu.com/problem.php?cid=1039&pid=15
我的暴力对拍代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 using namespace std; 6 7 typedef long long ll; 8 9 int ok( ll x) { 10 int cnt=0; 11 while(x ){ 12 cnt += (x%10==1); 13 x /= 10; 14 } 15 return cnt; 16 } 17 18 ll count(ll x, ll y){ 19 ll cnt = 0; 20 for ( ll i=x; i<=y; ++i ) 21 cnt += ok(i); 22 return cnt; 23 } 24 25 int main(){ 26 int l, r; 27 while( cin >> l >> r) { 28 cout << count(l,r) << endl; 29 } 30 }
按位dp代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 using namespace std; 6 7 typedef long long ll; 8 ll dp[12][10]; // 1的个数 9 ll is[12][10][2]; // 区间数中存在1的数 10 11 void Pre(){ 12 memset(is, 0, sizeof is); 13 memset(dp, 0, sizeof dp); 14 for ( int i=0; i<10; ++i ) 15 is[1][i][0] = 1; 16 is[1][1][1] = 1, is[1][1][0] = 0; 17 dp[1][1] = 1; 18 for ( int i=2; i<11; ++i ){ 19 for ( int j=0; j<10; ++j ) 20 { 21 for ( int k=0; k<10; ++k ){ 22 dp[i][j] += dp[i-1][k]; 23 if(j == 1) dp[i][j] += is[i-1][k][1] + is[i-1][k][0]; 24 // 特殊考虑这个点 25 26 is[i][j][1] += is[i-1][k][1]; 27 if(j == 1 ) is[i][j][1] += is[i-1][k][0]; 28 if(j == 1) continue; // is[i][1][0] = 0; 这个是一定的 29 is[i][j][0] += is[i-1][k][0]; 30 } 31 } 32 } 33 /*for ( int i=1; i<12;puts(""), ++i ) 34 for ( int j=0; j<10; ++j ) 35 cout << dp[i][j] <<" ";*/ 36 }; 37 38 ll get(int *a, int r, int l){ 39 if(r < l) return -1; 40 ll rt = 0; 41 for ( int i=r; i>=l; --i ) 42 rt= rt*10 + a[i]; 43 return rt; 44 }; 45 46 ll count( ll n ) { 47 if(n <= 0) return 0; 48 ll ret = 0; 49 int a[14]={0}; 50 while( n ) { 51 a[++a[0] ] = n%10; n /= 10; 52 } 53 54 while(a[0] > 0) { 55 int t = a[a[0] ]; 56 for ( int i=0; i<t; ++i ) 57 ret += dp[a[0] ][i]; 58 if(t == 1 ) ret += get(a, a[0]-1, 1)+1; 59 //t==1 注意这个1对后面数有贡献的 60 if(a[0] == 1) ret += dp[1][t];//考虑最后一位是否可取 61 --a[0]; 62 } 63 return ret; 64 } 65 66 int main(){ 67 Pre(); 68 ll L, R; 69 while ( scanf("%lld%lld", &L, &R) != EOF ) { 70 if(L > R) swap(L, R); 71 ll l = count(L-1); 72 ll r = count(R); 73 printf ("%lld\n", r-l); 74 } 75 }
仔细看我的代码的话会发现这句: if( t == 1) ret += get(a, a[0]-1, 1)+1; 这句是悲剧的体现。因为当数据全是1时,这个算法会退化成o(len*len); 退化的很是厉害。
在知道这道题有规律后,我也试着找了规律。
highNum: 当前位前面的数字;
lowNum:当前位后面的数字;
factor:10^x(x=0,1,2,3...);
如果当前位为0: ret += highNum*factor;
如果当前位为1:ret += highNum*factor+lowNum+1;
如果当前位大于1:ret += (highNum+1)*factor;
代码如下:
1 #include <iostream> 2 #include <cstdio> 3 #include <cmath> 4 #include <cstring> 5 #include <algorithm> 6 using namespace std; 7 8 typedef long long ll; 9 10 ll count( ll n ){ 11 if(n <= 0) return 0; 12 int currentNum; 13 ll highNum=0, lowNum=0; 14 ll ret = 0, factor = 1; 15 while( n/factor ) { 16 currentNum = n/factor%10; 17 highNum = n/(factor*10); 18 lowNum = n%factor; 19 if(currentNum == 0) ret += highNum*factor; 20 else if(currentNum == 1) ret += highNum*factor+lowNum+1; 21 else ret += (highNum+1)*factor; 22 factor *= 10; 23 } 24 return ret; 25 }; 26 27 28 int main(){ 29 ll L, R; 30 while( scanf("%lld%lld", &L, &R) != EOF) { 31 ll l = count(L-1); 32 ll r = count(R); 33 printf("%lld\n", r-l); 34 } 35 return 0; 36 } 37 // 不知道是九度oj的问题,还是这封代码的问题,这封代码竟然不能通过。。。 38 // 不过我测试了很多数据,这个是没有问题的
其实在推到过程中好像有组合数学的知识也是可以解决这个问题的,没有深入想了,数学弱啊。。
注:这两个算法都是可以推到到求[x,y]内任意一个数字(0,1,2,3....9)出现的次数;