泡沫

博客园 首页 联系 订阅 管理

编程之美这本书还是挺不错的,很多分析问题的方法都是让人受益的。算了废话什么的就不讲了。

如何求解区间[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

我的暴力对拍代码: 

View Code
 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代码:

View Code
 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;  

代码如下:

View Code
 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)出现的次数;

posted on 2013-04-21 18:41  木-天空  阅读(1152)  评论(0编辑  收藏  举报