数码(4月6日题目 枚举优化 整数分块)

数码

链接:https://ac.nowcoder.com/acm/problem/13221
题解:https://ac.nowcoder.com/discuss/399522

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

思路:
整数分块。计算 [ 1 , r ] [1,r] [1,r] 数字1~9出现的次数,减去 [ 1 , l − 1 ] [1,l-1] [1,l1] 数字1~9出现的次数。
区间 [ 1 , R ] [1,R] [1,R] 中的任意数 x x x 的因子是成对出现的,我们把它变成 a × b a\times b a×b 的形式(保证 a < b a<b a<b 避免重复取值, a ≤ R a\leq\sqrt R aR ),枚举 a a a,则 b b b [ 1 , R ] [1,R] [1,R] 的可取值范围就是 [ a + 1 , ⌊ R / a ⌋ ] [a+1,\lfloor{R/a}\rfloor] [a+1,R/a],并且 b b b 是连续的,只需要把这个范围内的数分别对应处理最高位即可,这里就分块,同样 a a a 用到的次数是 b b b 的次数,并且还多 a × a a\times a a×a 的一次。

Code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll l, r, a[10], b[10];
ll high(ll x)
{
    while (x >= 10)
        x /= 10;
    return x;
}
void calc(ll r, ll *arr)
{
    for (ll a = 1; a * a <= r; a++) //枚举因子a
    {
        ll b = r / a;                   //a固定时因子b的范围[a+1,b]
        for (ll i = 1; i <= r; i *= 10) //枚举因子b的位数
            for (ll j = 1; j <= 9; j++) //枚举因子b的最高位
            {
                ll L = max(j * i, a + 1);       //在i和j的范围下因子b的左边界
                ll R = min((j + 1) * i - 1, b); //b的右边界
                if (R - L >= 0)
                    arr[j] += R - L + 1;
            }
        ll x = high(a);
        arr[x] += b - a + 1; //a对应b的范围[a+1,b], 外加一次a*a, 用了 b-a+1 次
    }
}
int main()
{
    cin >> l >> r;
    calc(r, a);
    calc(l - 1, b);
    for (int i = 1; i <= 9; i++)
        cout << a[i] - b[i] << endl;
    return 0;
}

第二种写法:
看个例子就好了,比如 n u m = 9 num=9 num=9 那么最后决定答案的是什么?不就是 [ 9 ] , [ 90 , 99 ] , [ 900 , 999 ] , [ 9000 , 9999 ] . . . . . . [9],[90,99],[900,999],[9000,9999]...... [9],[90,99],[900,999],[9000,9999]...... 在范围内的倍数如 [ 9 , 18 , 27 , 36...... ] , [ 90 , 180 , 270 , 360 , . . . . . . , 91 , 182 , 273 , . . . . . . , 92 , . . . . . . ] , [ 900 , . . . . . . ] , . . . . . . [9,18,27,36......],[90,180,270,360,......,91,182,273,......,92,......],[900,......],...... [9,18,27,36......],[90,180,270,360,......,91,182,273,......,92,......],[900,......],......,这些倍数就是由前面区间中的数作为因子得到的 x x x,比如 R = 100900 R=100900 R=100900,在 [ 901 , 902 , . . . , 909 ] [901,902,...,909] [901,902,...,909] 的符合的倍数的个数都为 111 111 111 个,所以就有 111 × 9 111\times 9 111×9 个最高位为 9 9 9 的因子,然后就可以把这个区间一起算,然后继续往下找倍数的个数相同的因子区间。(https://blog.nowcoder.net/n/16f97f30e14f428d8c3103b8ee0df34e

Code2:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll calc(ll x, ll h) //右边界x,最高位h
{
    ll res = 0;
    for (ll t = 1; t <= x / h; t *= 10) //枚举最高位是h的位数
    {
        ll L = h * t, R = min(x, L + t - 1); //最高位是h的因子 [L,R]
        for (ll i = L, j; i <= R; i = j + 1)
        {
            j = min(x / (x / i), R);
            res += (j - i + 1) * (x / i); //范围为[i,j]内的因子,在范围内的倍数个数为x/i个
        }
    }
    return res;
}
int main()
{
    ios::sync_with_stdio(false);
    ll l, r;
    cin >> l >> r;
    for (int i = 1; i <= 9; i++)
        cout << calc(r, i) - calc(l - 1, i) << endl;
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值