数码 思维+模拟 牛客

数码

题目描述
给定两个整数 l 和 r ,对于所有满足1 ≤ l ≤ x ≤ r ≤ 10^9 的 x ,把 x 的所有约数全部写下来。对于每个写下来的数,只保留最高位的那个数码。求1~9每个数码出现的次数。
输入描述:
一行,两个整数 l 和 r (1 ≤ l ≤ r ≤ 10^9)。
输出描述:
输出9行。
第 i 行,输出数码 i 出现的次数。

题意:中文题
思路:首先把问题变为1到l-1和1到r的每个数码出现的次数相减,这样可以不用考虑那么多边界上的问题,
之后对题意进行分析,1e9的数据肯定不可能是O(n)的复杂度只能是O(sqrt(n))或者O(log(n))之类的复杂度,如果只对单个数x来进行分析,可以知道他的约数是成对存在的,(这里暂时不考虑平方的状态),那么就意味着我可以把时间变为O(sqrt(n)),但是我们要求的是1到r范围内所有x的所有约数,这就直接一个O(n),但可以这么想,如果我枚举约数的话不就是O(sqrt(n))了吗,
那么现在进行约数的枚举,在【1,r】范围内,要计算约数x出现的次数,只需要用r/x即可,举个例子,假设x=3,在【1,6】的范围内,约数3可以出现的次数为2,相对应的另外两个约数分别为1,2,如果把范围再扩大一点可以发现例如范围为【1,100】,可以出现33次,另外的约数分别为1到33,(如果仔细一点可以发现,如果每次都这么算的话,会有重复的约数被计算到,x=3时,另外两个约数1和2其实在x=1或2时,已经把3的情况算进去了,所以这里我门要规定一个下界,大于等于x+1),那么现在要进行的是对1到33这些约数进行处理,我们要统计每一个约数最高位的数码出现的次数,这里用到log(n)的复杂度,没有太大影响,也就是对可能出现的约数进行一遍遍历即可。(可能思路写的有点乱,但大体上应该是没有问题的)

参考文章:https://ac.nowcoder.com/discuss/399522

#pragma GCC optimize("Ofast","inline","-ffast-math")
#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<bits/stdc++.h> 
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
#define rep(i, a, n) for(int i = a; i < (int)n; i++)
#define per(i, a, n) for(int i = (int)n-1; i >= a; i--)
#define IOS std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define fopen freopen("file.in","r",stdin);freopen("file.out","w",stdout);
#define fclose fclose(stdin);fclose(stdout);
const int inf = 1e9;
const ll onf = 1e18;
const int maxn = 1e5+10;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while (isdigit(ch)){x=(x<<3)+(x<<1)+ch-48;ch=getchar();}
    return x*f;
}
int cal(int x){
    while(x>=10) x/=10;
    return x;
}
void solve(int x, int *a){
    for(int i = 1; i*i <= x; i++){      // 枚举除数的取值
        int tmp = x/i;  // 得到商的值
        for(int j = 1; j <= x; j*=10){  // 这里是对位数的枚举log(n)的复杂度
            for(int k = 1; k < 10; k++){
                int l = max(i+1, j*k);  // 去除重复计算的数,所以商要大于i,因为之前计算i的时候进行了计数,下界
                int r = min(tmp, j*(k+1)-1);    // 商一定小于等于tmp,上界
                if(r>=l) a[k] += r-l+1;
            }
        }
        a[cal(i)] += tmp-i+1;   // 计算i的首部使用的次数
    }
}
int a[10], b[10];
signed main(){
    int l = read(), r = read();
    solve(l-1, a);
    solve(r, b);
    for(int i = 1; i < 10; i++){
        b[i] -= a[i];
    }
    for(int i = 1; i < 10; i++){
        printf("%d\n", b[i]);
    }
    return 0;   
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值