初涉数论分块

数论分块:应该算是一类思想

什么是数论分块

我对数论分块的理解就是:在一类要统计$\sum_{i}^{n}{f(i)}$的数学题中,由于$f(i)$是单调的,故存在$x,y \in [i,j]$使得$f(x)=f(y)$。于是只要找到这段区间就可以节省计算区间内每一个函数值的时间开销。

时间复杂度大抵是$O(\sqrt n)$的?

数论分块入门题

【$\sum_{i}^{}{⌊\frac{n}{i}⌋}$】例一:bzoj1968: [Ahoi2005]COMMON 约数研究

Description

 

初涉数论分块

Input

只有一行一个整数 N(0 < N < 1000000)。

Output

只有一行输出,为整数M,即f(1)到f(N)的累加和。
 

 题目分析

答案即为$1..x$的所有约数个数和。

我们知道换种形式答案就是$\sum_{i}^{}{⌊\frac{n}{i}⌋}$。

那么暴力算法来了:所以我们

1 for (int i=1; i<=n; i++)
2     ans += n/i;

就好了。

由于$n=10^6$,所以这个$O(n)$的算法是能够过去的。但是不行!这个是数论分块的板子题,我们怎么能够止步于$O(n)$的算法呢?

若用$g(i)$表示$\frac{n}{i}$,显然$ans=\sum_{i=1}^{n}{g(i)}$,并且$g(i)$是并不严格单调的。

自然而然地想到,在求$g(i)$的同时能不能够求出$[i,j]$呢?

这里有一个结论,$j=⌊{\frac{n}{⌊\frac{n}{i}⌋}}⌋$,下面来证明这个结论。

我们有 $j=⌊{\frac{n}{⌊\frac{n}{i}⌋}}⌋ \leq {\frac{n}{⌊\frac{n}{i}⌋}}<j+1$,因此得到

\begin{cases}⌊\frac{n}{i}⌋\leq \frac{n}{j}\\\frac{n}{j+1}<{⌊\frac{n}{i}⌋}\end{cases}

故$j$是满足条件的最大值。

 

 1 #include<bits/stdc++.h>
 2 typedef long long ll;
 3 
 4 int n;
 5 ll ans;
 6 
 7 int main()
 8 {
 9     register int i,j;
10     scanf("%d",&n);
11     for (i=1; i<=n; i=j+1)
12         j = n/(n/i),
13         ans += 1ll*(j-i+1)*(n/i);
14     printf("%lld\n",ans);
15     return 0;
16 }

 

【$\sum_{i}^{}{⌊\frac{n}{i}⌋}$变式】例二:

题目描述

给定两个整数lr

对于任意x,满足 l≤x≤r,把x的所有约数全部写下来。

对于每个写下来的数,只保留最高位的那个数码。求[1,9]中每个数码出现的次数。

数据范围

对于100%的数据:$1≤l≤r≤10^9$


题目分析

这里略有不同的是,每个因数只保留最高位的数码,相当于就是答案贡献加在了不同的地方上。

很显然的是这个答案是可减的,于是我们就可以用类似前缀和的思想依次处理$[1,9]$。

 1 #include<bits/stdc++.h>
 2 typedef long long ll;
 3 
 4 int l,r;
 5 
 6 ll work(ll x, ll a, ll b)
 7 {
 8     ll ret = 0, j = 0;
 9     for (ll i=a; i<=b; i=j+1)
10         j = std::min(x/(x/i), b),
11         ret += 1ll*(j-i+1)*(x/i);
12     return ret;
13 }
14 ll get(ll x, ll p)
15 {
16     ll ret = 0, t = 1;
17     for (; x>=p; t*=10, p*=10)
18         ret += work(x, p, std::min(x, p+t-1));
19     return ret;
20 }
21 int main()
22 {
23     scanf("%d%d",&l,&r);
24     for (int i=1; i<=9; i++)
25         printf("%lld\n",get(r, i)-get(l-1, i));
26     return 0;
27  }

这里$get(x,p)$表示$1..x$个数中,因数最高位为$p$的答案总数。例如$p=3$,那么最高位为$p$的数一定是$300..399$,$3000...3999$这样的数。

之后的$work(x,a,b)$则表示$a..b$之间是$x$因数的个数。这一步就转变成了$\sum_{i}^{}{⌊\frac{n}{i}⌋}$的模型,于是就可以用数论分块做了。

至此看上去很奇怪的这道题就转化成了那个普通模型了。

 

【$\sum_{i}^{}{⌊\frac{n}{i}⌋}$变式】例三:1257: [CQOI2007]余数之和

Description

给出正整数n和k,计算j(n, k)=k mod 1 + k mod 2 + k mod 3 + … + k mod n的值
其中k mod i表示k除以i的余数。
例如j(5, 3)=3 mod 1 + 3 mod 2 + 3 mod 3 + 3 mod 4 + 3 mod 5=0+1+0+3+3=7

Input

输入仅一行,包含两个整数n, k。
1<=n ,k<=10^9

Output

输出仅一行,即j(n, k)。


题目分析

这里的mod操作虽然不单调递增,看上去好像很玄学好像要转化成其他模型的样子,但是我们把式子变化一下:

$$=\sum_{i=1}^{n}{(K-i*⌊\frac{K}{i}⌋)}  \\
=n∗K−\sum_{i=1}^{}{}⌊\frac{K}{i}⌋$$

于是我们发现又回到基本的模型了!

 1 #include<bits/stdc++.h>
 2 typedef long long ll;
 3  
 4 ll n,k,ans;
 5  
 6 int main()
 7 {
 8     register int i,j;
 9     scanf("%lld%lld",&n,&k);
10     ans = n*k;
11     for (i=1; i<=n; i=j+1)
12     {
13         if (!(k/i)) break;
14         j = std::min(k/(k/i), n);
15         ans -= 1ll*(j+i)*(j-i+1)/2*(k/i);
16     }
17     printf("%lld\n",ans);
18     return 0;
19 }

 

 

 

END

i=1n(KiKi)i=1n(KiKi)

原文链接: https://www.cnblogs.com/antiquality/p/9216301.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    初涉数论分块

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/276521

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月15日 上午1:47
下一篇 2023年2月15日 上午1:48

相关推荐