数码(每日一题)除法分块

题意
给定两个整数 l 和 r ,对于所有满足1 ≤ l ≤ x ≤ r ≤ 10^9 的 x ,把 x 的所有约数全部写下来。对于每个写下来的数,只保留最高位的那个数码。求1~9每个数码出现的次数。
思路
我们刚看到这个题,肯定先想到的是暴力,也就是枚举l~r里面的每一个数,然后计算每一个数的约数,最后再输出。
暴力枚举
这样的复杂度是( ( r − l ) ∗ s q r t (r-l)*sqrt (rl)sqrt),l到r的区间最大可以到1e9,就光枚举这一层就会超时了,所以说这种枚举方式肯定是不行的。
我们思考一下,枚举l~r里面的数,是不是可以转换成枚举
1 ~ r里面的约数,然后再减去1~l-1之间的约数。
因为这样连续的数会出现一些特殊的性质,会更好的处理问题。
我们如果一个一个的枚举每一个数,然后求约数的话,这样肯定是会超时的。
我们可以先枚举1~r中所有数约数有1的个数。
这个怎么枚举呢。
我们可以思考。
2 ∗ 1 = 2 2*1=2 21=2
2 ∗ 2 = 4 2*2=4 22=4
2 ∗ 3 = 6 2*3=6 23=6
2 ∗ 4 = 8 2*4=8 24=8
2 ∗ 5 = 10 2*5=10 25=10
我们固定2,然后求出2的所有倍数,这些数如果都小于r的话,说明2,4,6,8,10这些数中都有约数2。
举个例子:
如果r=233的时候。
我们就很容易的求出约数为1的个数有 233 / 1 = 233 233/1=233 233/1=233
我们就很容易的求出约数为2的个数有 233 / 2 = 116 233/2=116 233/2=116
这样1~9的所有约数就都很容易求出来了。
当约数大于9的时候,就需要考虑约数的最高位了。
当约数为
10~19
100~199
1000~1999的时候,我们发现其实他们最后也都是1。
20~29
200~299
2000~2999的时候,我们发现其实他们最后也都是1。
如果我们还用刚才上面的方法,用每个约数除那个r,这样随便也能得到结果。
但是当约数为100000000~199999999的时候,光枚举都已经超时了。
说明我们还需要优化。
这里就要引出除法分块,他可以把区间为n优化到 n \sqrt{n} n
就比如约数为100000000~199999999的时候,本来需要跑1e8的循环,但是用除法分块,就只需要 1 e 8 \sqrt{1e8} 1e8
具体是怎么实现的呢,我们举个小例子。
21/1=21,而21/21=1
21/2=10,而21/10=2
21/3=7,而21/7=3
21/4=5,而21/5=4
21/5=4,而21/4=5
是不是觉得很无聊?接下来你就会发现神奇的地方
21/6=3,而21/3=7(说明1到21能整除6和整除7的都是3个,我们可以不管7直接跳到8)
21/8=2,而21/2=10(说明1到21能整除8,9,10都是2个,我们可以不管9,10直接跳到11)
21/11=1,而21/1=21(说明1到21能整除11 21的都是1个,我们直接跳到22,此时已经结束)
我们发现他们被分成一个个块,块的大小为(n/(n/i)−i+1)
这样的复杂度就是 n \sqrt{n} n
所以我们总的复杂度就是 9 ∗ l o g 10 n ∗ n 9*log10n*\sqrt{n} 9log10nn
代码

#include <bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pb push_back
#define me memset
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
using namespace std;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
ll solve(ll x,ll c)
{
    ll res=x/c;
    for(ll be=10*c,ed=10*c+9 ;  ; be*=10,ed=ed*10+9)
    {
        ll realr=min(ed,x);
       // if(x<be) break;
        for(ll i=be ; i<=realr ; i++)
        {
            ll cc1=x/i;
            ll cc2=x/cc1;
            cc2=min(realr,cc2);
            res+=(cc1*(cc2+1-i));
            i=cc2;
        }
        if(realr==x) break;
    }
    return res;
}
int main()
{
    int l,r;
    cin>>l>>r;
    for(int i=1 ; i<=9 ; i++)
    {
        cout<<solve(r,i)-solve(l-1,i)<<endl;
     //   cout<<solve(r,i)<<" "<<solve(l-1,i)<<endl;
    }
    return 0;
}
/*
1 10000
30554
16652
11415
8938
6610
5707
5051
4564
4177
*/

总结
这一题让我学习了许多,让我认识到了一种算法,除法分块。
除法分块例题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值