数论分块简介

发布时间 2023-03-31 13:05:29作者: Johnson-Hugo

简单介绍一下数论分块的思想。空说无益,先上几道题。

题1:P1403 约数研究

链接如下:https://www.luogu.com.cn/problem/P1403

 

 

如果这道题要对每一个数进行分解、统计,未免太麻烦。我们不妨换个思路,假设这里的N是30,那么这个区间内整体的数字分布如下图:

 

 

 这里我们先选择求解有多少数会以2为因子,这很好求,任何形如2k的数都满足这个条件,这里标黄,如下图所示:

 

 

 因为在这个区间内k的变化是连续的(自然数意义上),所以我们要求得有多少k在这里满足需求,只需要将N除以因子2即可,在本例中k是等于15。

再考虑当因子为3时的情况,如下图,标蓝的数字即为满足要求的数字:

 

 

 这里的k也很好求,等于10

再考虑当因子等于25时会有几个数满足要求,很显然只有25自己满足。

这些因子出现次数的求解不是无止境的,因为因子的数量不可能大于N本身,所以我们便将上述问题转换为如下形式:

 

 

 即便将问题转换为如此方便的形式,我们依然可以更进一步,这里以N=15为例,我们观察一下上述求和的运作过程:

 

 

 我们观察到上述存在着区间内值相等的情况,我们可以考虑使用分块的思想。

假设左边界为i,现在我们需要求右边界可以延伸到何处。先确定此处的函数值为N/i,我们可以知晓后续的k满足

 

这里满足要求的k的取值即为

 

 

 这里的k即为满足要求的右边界,在左右边界内的函数值都是相同的,那求和也就很简单,直接区间函数值乘以区间长度即可。

可以证明这个方法的时间复杂度为,具体不做证明

 

 

上题的代码如下:

 1 #include <iostream>
 2 #define ll long long
 3 using namespace std;
 4 ll n;
 5 void solve(ll n1){
 6     ll ans=0;
 7     ll r=0;
 8     for (int i=1;i<=n1;i=r+1){
 9         r=n1/(n1/i);
10         ans+=(n1/i)*(r-i+1);
11     }
12     cout<<ans;
13 }
14 int main(){
15     cin>>n;
16     solve(n);
17     return 0;
18 }

题2:NC13221 数码

题目链接如下:https://ac.nowcoder.com/acm/problem/13221

题目截图如下:

 

 这里所用到的也是数论分块的思想,只不过改为了统计最高位数码出现的次数。这里需要采用前缀和的知识来记录每一个数量级对出现次数的贡献度,实现相对复杂。

代码如下:

 1 #include <iostream>
 2 #define ll long long
 3 using namespace std;
 4 ll b[15],rem[20],l,r;
 5 void get_num(ll num,ll opt){
 6     ll m=num;
 7     ll g=0,t=0;
 8     while(m){
 9         t++;
10         g=m%10;
11         m/=10;
12     }
13     for (int i=1;i<=9;i++)
14         rem[i]+=opt*b[t-1];
15     for (int i=1;i<g;i++)
16         rem[i]+=opt*(b[t]-b[t-1]);
17     rem[g]+=opt*(num-g*(b[t]-b[t-1])+1);
18 }
19 void solve(ll start,ll opt){
20     ll end=0;
21     for (int i=1;i<=start;i=end+1){
22         end=start/(start/i);
23         get_num(end, opt*start/i);
24         get_num(i-1,-1*opt*start/i);
25     }
26 }
27 int main(){
28     ll ans=1;
29     for (int i=1;i<=10;i++){
30         b[i]=b[i-1]+ans;
31         ans*=10;
32     }
33     cin>>l>>r;
34     solve(r,1);
35     solve(l-1,-1);
36     for (int i=1;i<10;i++)
37         cout<<rem[i]<<endl;
38     return 0;
39 }

题3:P2424 约数和

题目链接如下:https://www.luogu.com.cn/problem/P2261

题目截图如下:

 

 这一题主要改求解出现次数为出现数字和,那么整体的求和形式变为

 

 

 这里分区间考虑,在一个区间内是相同的,将形式转换为,在区间内做等差数列求和即可。

 

代码如下:

 1 #include <iostream>
 2 #define ll long long
 3 using namespace std;
 4 ll a1,a2;
 5 ll solve(ll n1){
 6     ll res=0;
 7     ll r=0;
 8     for (int l=1;l<=n1;l=r+1){
 9         r=n1/(n1/l);
10         res+=(n1/l)*(r-l+1)*(r+l)/2;
11     }
12     return res;
13 }
14 int main(){
15     cin>>a1>>a2;
16     cout<<solve(a2)-solve(a1-1);
17     return 0;
18 }