……

学习笔记:数论进阶(整除与约数)

前置知识:看这篇博客就好了:戳我
算是数论进阶吧(也不会太难)。

\(Part\;1\).因子个数定理

我们考虑一个整数\(a(a>1)\):

\[a=p_1^{k_1}p_2^{k_2}......p_n^{k_n} \]

也就是

\[a=\prod_{i=1}^n p_i^{k_i} \]

的约数有

\[\prod_{i=1}^n(k_i+1) \]

个。
证明:
我们考虑任意一个\(p\),含有\(p\)的因子可以包括\(p^0,p^1,p^2......p^k\),共\(k+1\)种取值,那么所有的质因子的取值组合就有

\[\prod_{i=1}^n(k_i+1) \]

个,且互不相同

\(Part\;2\).整除分块(数论分块)

我们如何求:

\[\sum\limits_{i=1}^n\left\lfloor\dfrac{n}{i}\right\rfloor \]

呢?
不如手玩一下(滑稽:
比如说\(n=10\),可以得到一个表格:

\(i\) 1 2 3 4 5 6 7 8 9 10
\(\left\lfloor\dfrac{n}{i}\right\rfloor\) 10 5 3 2 2 1 1 1 1 1

好了,容易发现取\(1,2\)两值的数有多个,考虑把他们一下子都筛掉。
我们考虑\(\left\lfloor\dfrac{n}{i}\right\rfloor\)的实际意义是:\(i\)这个数对应的取值,那么容易看出\(\left\lfloor\dfrac{n}{\left\lfloor\dfrac{n}{i}\right\rfloor}\right\rfloor\) 代表着为\(\left\lfloor\dfrac{n}{i}\right\rfloor\)值的最后一个数,我们就可以每发现一个新得数,直接跳就好了。
代码是这样的:

ll cal(ll x)
{
	if(x==1) return 1ll*1;//注意1要特例
	ll sum=0;
	for(ll i=1;i<=x;i++)
	{
		sum+=((x/(x/i)-i+1)*(x/i))%mod;
		i=x/(x/i);
	}
	return sum%mod;
}

复杂度是多少呢?
\(O(\sqrt{n})\)的,
证明:
分类讨论:
1.若\(i\leqslant \sqrt{n}\),那么\(\left\lfloor\dfrac{n}{i}\right\rfloor\) 的取值只有最多\(\sqrt{n}\)种。
2.若\(i<\sqrt{n}\),那么\(\left\lfloor\dfrac{n}{i}\right\rfloor\) 的取值也只有最多\(\sqrt{n}\)种。
综上最多只有\(2\sqrt{n}\)种取值,复杂度就是\(O(\sqrt{n})\)的。

当然有例题了,嘿嘿。
题目链接:P3935 Calculating
观察此题,发现\(f(i)\)不就是裸的约数个数定理吗???
易得:

\[f(n)=\sum\limits_{i=1}^n\sum\limits_{d|i}^i1 \]

然而我们要求的是区间和。

\[G(n)=\sum\limits_{i=1}^n\sum\limits_{d|i}^i1 \]

直接求\(G(r)-G(l-1)\)就行了。
我们考虑对\(G(n)\)化简。
考虑每个数的贡献,每个数\(x\)作为别统计的约数出现了\(\left\lfloor\dfrac{n}{x}\right\rfloor\)次。
此题得到的式子为:

\[\sum\limits_{i=1}^r\left\lfloor\dfrac{r}{i}\right\rfloor-\sum\limits_{i=1}^{l-1}\left\lfloor\dfrac{l-1}{i}\right\rfloor \]

数论分块求即可。

\(Code\):

#include<cstdio>
#include<iostream> 
#include<cstring>
using namespace std;
typedef long long ll;
const int mod=998244353;
ll l,r;
ll cal(ll x)
{
	if(x==1) return 1ll*1;
	ll sum=0;
	for(ll i=1;i<=x;i++)
	{
		sum+=((x/(x/i)-i+1)*(x/i))%mod;
		i=x/(x/i);
	}
	return sum%mod;
}
int main()
{
	scanf("%lld%lld",&l,&r);
	printf("%lld\n",(cal(r)-cal(l-1)+mod)%mod);
	return 0;
}

当然,在问及数论分块的板题时,更多人会说这个:
题目链接:P2261 [CQOI2007]余数求和
我们知道取模的另一种意义:

\[n\%m\Leftrightarrow n-m\left \lfloor \dfrac{n}{m}\right \rfloor \]

那我们来化简式子吧:

\[\sum\limits_{i=1}^nk\%i=\sum\limits_{i=1}^n(k-i\left \lfloor \dfrac{k}{i}\right \rfloor)=nk-\sum\limits_{i=1}^ni\left \lfloor \dfrac{k}{i}\right \rfloor \]

然后数论分块就好了。
不过还是需要注意一些问题的:
这里数论分块是有界的数论分块,为什么呢,我们对式子进行进一步化简:
设原式为\(S(n,k)\),那么:

\[S(n,k)= \begin{cases}nk-\sum\limits_{i=1}^ni\left \lfloor \dfrac{k}{i}\right \rfloor&n\leqslant k\\nk-\sum\limits_{i=1}^ki\left \lfloor \dfrac{k}{i}\right \rfloor&n>k\end{cases} \]

为什么呢,由于当\(i>k\)时,\(\left \lfloor \dfrac{k}{i}\right \rfloor=0\)
对于\(n\leqslant k\)的情况下,要特判下一段点与边界\(n\)的大小比较,以免越界。
这就是提高版的数论分块。

\(Code\)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define read(x) scanf("%lld",&x)
#define ll long long 
ll sum,n,k;
ll cal(ll n,ll m)
{
	ll ans=0;
	for(ll i=1;i<=m;i++)
	{
		ll r=min(m,n/(n/i));
		ans=ans+(i+r)*(r-i+1)/2*(n/i);//注意这里用到了等差数列求和公式
		i=r;
	}
	return ans;
}
int main()
{
	read(n),read(k);
	sum=n*k;
	printf("%lld\n",sum-cal(k,min(n,k)));
	return 0;
} 

应该能看懂吧......

posted @ 2020-03-14 12:01  童话镇里的星河  阅读(361)  评论(0编辑  收藏  举报