• 编程之美之算法 如何快速求解[x,y]区间内1出现的个数 木


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

    如何求解区间[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)出现的次数;

  • 相关阅读:
    Jquery中的this指向的是哪个对象?
    需要重新编辑
    关于CSS选择器优先级无冲突样式设置的展示
    在 CSS 中,width 和 height 指的是内容区域的宽度和高度
    关于正则表达式中分组的一些误解勘正以及String的replaceAll方法误解勘正
    关于informatica的Dynamic Lookup组件使用中遇到的一个问题的思考
    【转】Informatica Update 机制详解
    维度表和事实表的含义
    今天看IO流,复制word遇到的一个小问题
    小试下新博客,一个列传行的SQL
  • 原文地址:https://www.cnblogs.com/TengXunGuanFangBlog/p/how_to_count_the_number_of_1.html
Copyright © 2020-2023  润新知