计数问题

题目

P1179 [NOIP2010 普及组] 数字统计
题目描述
请统计某个给定范围[L,R]的所有整数中,数字2出现的次数。

比如给定范围[2,22],数字2在数2中出现了1次,在数12中出现1次,在数20中出现1次,在数21中出现>1次,在数22中出现2次,所以数字2在该范围内一共出现了6次。

输入格式
2个正整数L和R,之间用一个空格隔开。

输出格式
数字2出现的次数。
说明/提示
1≤L≤R≤100000

题解

1. 枚举法

只要遍历[L,R]的每个数的每位数字进行统计就可得到答案,复杂度是O(nlogn)

完整代码

#include <iostream>
using namespace std;
int main(){
int l,r;
cin>>l>>r;
int cnt = 0;
for(int i = l;i<=r;i++){
int tmp = i;
while(tmp){
if(tmp%10 == 2) cnt++;
tmp/=10;
}
}
cout<<cnt<<endl;
return 0;
}

这个复杂度对于这道题的数据量而言已经够了,但是数据量超过 108 量级后就不可接受了,需要用到公式法。

2.公式法

假设 f(n,num) 是从 [1,n] 所有整数出现数字 num 的次数和,那么想要得到 [L,R] 内所有整数出现数字 num 的次数和可以通过 f(r,num)f(l1,num) 得到,其中 0num1

我们可以通过考虑每位数字得到 f(n,num) :

1num9 的情况

假设一个 p 位正整数 n 的数位构成情况为 np1np2n1n0¯ ,其中 ni 是第 i 位数的数字 (0ip1)
考虑 [1,n] 中某个数 mp 位构成情况为 mp1mp2m1m0¯ (不够p位数可以补前导0)

  1. ni<num
  • mi+1p1 位的数字构成的新数小于 ni+1p1 位的数字构成的新数,即 mp1mi+1¯<np1ni+1¯
    那么 m 可以满足 mi=num 且无论第 i1 位及以后的数字如何也一定在 [1,n] 中。
  • mp1mi+1¯np1ni+1¯ 因为 ni<num ,如果此时使 mi=num ,那么 mp1mi+1mi¯>np1ni+1ni¯ 则无论 mi1 位及以后的数字如何都会 m>n 从而不在 [1,n] 的范围中。
  • 综上,当 ni<num 只有 mp1mi+1¯<np1ni+1¯ 时成立,且此时 [1,n]i 位出现 num 的次数为 np1ni+1¯×10i
  1. ni=num
  • mp1mi+1¯<np1ni+1¯ 时,次数 np1ni+1¯×10i 显然成立。
  • mp1mi+1¯=np1ni+1¯时,若 m 满足 mi=num ,那么只要 mi1m0¯ni1n0¯ 即可,此时次数为 ni1n0¯+1 我们约定 i=0 时,ni1n0¯=0
  • mp1mi+1¯>np1ni+1¯ 那么无论 mi1 位及以后的数字如何都会 m>n ,从而不在 [1,n] 的范围中。
  • 综上,当 ninum 只有 mp1mi+1¯np1ni+1¯ 时成立,且此时 [1,n]i 位出现 num 的次数为 np1ni+1¯×10i+ni1n0¯+1
  1. ni>num
  • mp1mi+1¯<np1ni+1¯ 时,次数 np1ni+1¯×10i 显然成立;
  • mp1mi+1¯=np1ni+1¯时,因为 ni>num ,若使 mi=num,则 mp1mi+1mi¯<np1ni+1ni¯ 从而无论第 i1 位及以后的数字如何也一定在 [1,n] 中,次数为 10i
  • mp1mi+1¯>np1ni+1¯ 那么无论 mi 位及以后的数字如何都会 m>n ,从而不在 [1,n] 的范围中。
  • 综上,当 ninum 只有 mp1mi+1¯np1ni+1¯ 时成立,且此时 [1,n]i 位出现 num 的次数为 (np1ni+1¯+1)×10i

num=0 的情况

这种情况下,我们知道如果某一位是 0 ,那么这个整数一定有不为 0 的更高位,就需要排除在上一种情况下 mp1mi+1¯=0 的一种可能了。显然在三个可能的 numni关系下,都可能出现这种情况,所以在每次计算完之后需要减去一个关于 np1ni+1¯ 的所有情况,即减去 10i

运用以上的结论,每次只需要遍历一次两个端点的位数,是 O(logn)的算法。

完整代码

#include <iostream>
#define ll long long
using namespace std;
ll f(ll n,ll num){
ll base = 1,cnt = 0;
while(base<=n){
ll pre = n/(base*10);
ll suf = n%base;
ll now = n/base%10;
if(now<num) cnt+=pre*base;
else if(now == num) cnt+=pre*base + suf + 1;
else cnt+=(pre+1)*base;
if(!num) cnt-=base;
base*=10;
}
return cnt;
}
int main(){
std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
ll l,r;
cin>>l>>r;
cout<<f(r,2)-f(l-1,2)<<endl;
}
posted @   空白菌  阅读(185)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
点击右上角即可分享
微信分享提示