计数问题

2022/2/7 6:12:30

本文主要是介绍计数问题,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

计数问题

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;
}

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

2.公式法

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

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

\(1\leq num\leq 9\) 的情况

假设一个 \(p\) 位正整数 \(n\) 的数位构成情况为 \(\overline{n_{p-1}n_{p-2}\cdots n_1n_0}\) ,其中 \(n_i\) 是第 \(i\) 位数的数字 \((0\leq i\leq p-1)\) 。
考虑 \([1,n]\) 中某个数 \(m\) 其 \(p\) 位构成情况为 \(\overline{m_{p-1}m_{p-2}\cdots m_1m_0}\) (不够p位数可以补前导0)

  1. \(n_i<num\)
  • 若 \(m\) 的 \(i+1\) 到 \(p-1\) 位的数字构成的新数小于 \(n\) 的 \(i+1\) 到 \(p-1\) 位的数字构成的新数,即 $$\overline{m_{p-1}\cdots m_{i+1}}<\overline{n_{p-1}\cdots n_{i+1}}$$
    那么 \(m\) 可以满足 \(m_i=num\) 且无论第 \(i-1\) 位及以后的数字如何也一定在 \([1,n]\) 中。
  • 当 $$\overline{m_{p-1}\cdots m_{i+1}}\geq \overline{n_{p-1}\cdots n_{i+1}}$$ 因为 \(ni<num\) ,如果此时使 \(m_i=num\) ,那么 $$\overline{m_{p-1}\cdots m_{i+1}m_i}>\overline{n_{p-1}\cdots n_{i+1}n_i}$$ 则无论 \(m\) 第 \(i-1\) 位及以后的数字如何都会 \(m>n\) 从而不在 \([1,n]\) 的范围中。
  • 综上,当 \(n_i<num\) 只有 $$\overline{m_{p-1}\cdots m_{i+1}}<\overline{n_{p-1}\cdots n_{i+1}}$$ 时成立,且此时 \([1,n]\) 第 \(i\) 位出现 \(num\) 的次数为 $$\overline{n_{p-1}\cdots n_{i+1}}\times10^i$$
  1. \(n_i=num\)
  • 当 $$\overline{m_{p-1}\cdots m_{i+1}}<\overline{n_{p-1}\cdots n_{i+1}}$$ 时,次数 $$\overline{n_{p-1}\cdots n_{i+1}}\times 10^i$$ 显然成立。
  • 当 $$\overline{m_{p-1}\cdots m_{i+1}}=\overline{n_{p-1}\cdots n_{i+1}}$$时,若 \(m\) 满足 \(m_i=num\) ,那么只要 $$\overline{m_{i-1}\cdots m_0}\leq \overline{n_{i-1}\cdots n_0}$$ 即可,此时次数为 $$\overline{n_{i-1}\cdots n_0}+1$$ 我们约定 \(i=0\) 时,$$\overline{n_{i-1}\cdots n_0}=0$$
  • 当 $$\overline{m_{p-1}\cdots m_{i+1}}> \overline{n_{p-1}\cdots n_{i+1}}$$ 那么无论 \(m\) 第 \(i-1\) 位及以后的数字如何都会 \(m>n\) ,从而不在 \([1,n]\) 的范围中。
  • 综上,当 \(n_i\geq num\) 只有 $$\overline{m_{p-1}\cdots m_{i+1}}\leq \overline{n_{p-1}\cdots n_{i+1}}$$ 时成立,且此时 \([1,n]\) 第 \(i\) 位出现 \(num\) 的次数为 $$\overline{n_{p-1}\cdots n_{i+1}}\times 10^i+\overline{n_{i-1}\cdots n_0}+1$$
  1. \(n_i>num\)
  • 当 $$\overline{m_{p-1}\cdots m_{i+1}}<\overline{n_{p-1}\cdots n_{i+1}}$$ 时,次数 $$\overline{n_{p-1}\cdots n_{i+1}}\times 10^i$$ 显然成立;
  • 当 $$\overline{m_{p-1}\cdots m_{i+1}}=\overline{n_{p-1}\cdots n_{i+1}}$$时,因为 \(n_i>num\) ,若使 \(m_i=num\),则 $$\overline{m_{p-1}\cdots m_{i+1}m_i}<\overline{n_{p-1}\cdots n_{i+1}n_i}$$ 从而无论第 \(i-1\) 位及以后的数字如何也一定在 \([1,n]\) 中,次数为 \(10^i\) 。
  • 当 $$\overline{m_{p-1}\cdots m_{i+1}}> \overline{n_{p-1}\cdots n_{i+1}}$$ 那么无论 \(m\) 第 \(i\) 位及以后的数字如何都会 \(m>n\) ,从而不在 \([1,n]\) 的范围中。
  • 综上,当 \(n_i\geq num\) 只有 $$\overline{m_{p-1}\cdots m_{i+1}}\leq \overline{n_{p-1}\cdots n_{i+1}}$$ 时成立,且此时 \([1,n]\) 第 \(i\) 位出现 \(num\) 的次数为 $$(\overline{n_{p-1}\cdots n_{i+1}}+1)\times 10^i$$

\(num=0\)的情况

这种情况下,我们知道如果某一位是 \(0\) ,那么这个整数一定有不为 \(0\) 的更高位,就需要排除在上一种情况下 \(\overline{m_{p-1}\cdots m_{i+1}}=0\) 的一种可能了。显然在三个可能的 \(num\) 和 \(n_i\)关系下,都可能出现这种情况,所以在每次计算完之后需要减去一个关于 \(\overline{n_{p-1}\cdots n_{i+1}}\) 的所有情况,即减去 \(10^i\) 。

运用以上的结论,每次只需要遍历一次两个端点的位数,是 \(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;
}


这篇关于计数问题的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程