统计区间[a,b]各个数字出现的个数:poj 2282 The Counting Problem poj 3286

组合数学中体现分析功底的基础题

题目大意
给定一个区间[a,b],统计区间内0,1,2,3,4,5,6,7,8,9各个数字出现的个数。

解题思路
问题转化为(0,n】区间各个数字出现的个数(最后用b减去a-1就是结果),关键是怎样去计数呢?
举个例子大家就会非常明白,以2345为例,分区间
————
0 0 0 1

1 0 0 0

2 0 0 0
————
2 0 0 1

2 3 0 0
————
2 3 0 1

2 3 4 0
————
2 3 4 1

2 3 4 5
————
第一个区间,先取得最高位2,高位上的0,1出现的次数为1000次,而2出现的次数是000~345,即346次;再看低位000~999,0~9出现的次数是相同的3*1000/10,高位0,1决定最终结果×2,即0~9次数加上2×3×1000/10;

注意这一步多考虑了前导零,而且只有在这一步,接下来的区间内并不会出现前导零,所以可以先不用管,等把所有区间都解决了,最后再减去前导零。

接下来分析第二个区间2001~2300.同样地,先取得最高位3(此时2的所有情况在第一个区间已经计数完毕,最高位已经失去意义),从001~300,高位上0,1,2出现的次数是100次,(0不是前导零啦!),低位00~99中0~9各出现3×2×100/10,分别计数;

以此类推至第3、4区间2301~2340,2341~2345,循环处理相同。

最后,不要忘记把多算的前导零减去,千位上的0多算了1000次,百位100,视为10,个位1,即最后减去1000+100+10+1

参考代码+部分解释

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <vector>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int maxn = 7e4+10;
int a,b,f[10]={0},g[10]={0};
void swap(int &a,int &b){int temp=a;a=b;b=temp;return;}
void solve(int n,int *h)//计算(0,n]区间0123456789出现的次数,记录在数组f[]中,以2345为例
{
   if(n==0) return;
   int m=n,len=0;while(n){len++;n/=10;};n=m;      //得到长度len,用m恢复n

   for(int i=len;i>0;i--){ //从高位向低位一个一个处理

    int temp=(int)pow(10.0,i-1),digit=n/temp;     //取高位digit //处理高位
    for(int j=0;j<digit;j++) h[j]+=temp;h[digit]+=n%temp+1;

    int res=digit*(i-1)*temp/10;for(int i=0;i<10;i++) h[i]+=res;
    n%=temp;
  }
   for(int i=len;i>0;i--) h[0]-=(int)pow(10.0,i-1);//对多余0的处理
   return;
}
int main()
{
 //  freopen("input.txt","r",stdin);
   while(cin>>a>>b&&a+b){
     memset(f,0,sizeof(f));
     memset(g,0,sizeof(g));
     if(a>b) swap(a,b);
     solve(a-1,f);
     solve(b,g);
     for(int i=0;i<10;i++)
      if(i) cout<<" "<<g[i]-f[i];else cout<<g[i]-f[i];
     cout<<endl;
   }
   return 0;
}


poj 3286: How many 0’s?买一送一~

题目大意

统计区间[a,b]中数字0出现的个数

解题思路

是poj2282的一部分,解题思路是一样的,但是发现了poj2282算法中的一出问题(浮点数精度误差),强制转换double为int会有误差的,比如temp=100,实际会生成99,所以在这里我重新定义了一个变量x记录1000->100->10->1,避免了浮点数误差问题。

还要要注意的是开始区间m=0的问题,m-1是-1,求出来的结果与实际不符,所以我还是特判了一下m=0的情况,从1开始的仍然按照上一道题的处理方法解决。

参考代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <vector>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int maxn = 7e4+10;
ll m,n;
ll solve(ll n)//计算(0,n]区间0出现的次数,记录在ans中
{
   ll ans=0;
   ll m=n,len=0,x=1;while(n){len++;n/=10;x*=10;};n=m;      //得到长度len,用m恢复n
   for(ll i=len;i>0;i--){ //从高位向低位一个一个处理
    x/=10;int temp=x,digit=n/temp; //取高位digit //处理高位
    if(digit>0) ans+=temp;else ans+=n%temp+1;
    ll res=digit*(i-1)*temp/10;ans+=res;
    n%=temp;
  }
   x=1;
   for(int i=0;i<len;i++) {ans-=x;x*=10;}//对多余0的处理
   return ans;
}
int main()
{
  // freopen("input.txt","r",stdin);
   while(cin>>m>>n&&(m>=0)){
    if(m==0) cout<<solve(n)+1<<endl;
    else cout<<solve(n)-solve(m-1)<<endl;
   }
   return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值