前置知识:看这篇博客就好了:戳我
算是数论进阶吧(也不会太难)。
(Part;1).因子个数定理
我们考虑一个整数(a(a>1)):
也就是
的约数有
个。
证明:
我们考虑任意一个(p),含有(p)的因子可以包括(p^0,p^1,p^2......p^k),共(k+1)种取值,那么所有的质因子的取值组合就有
个,且互不相同。
(Part;2).整除分块(数论分块)
我们如何求:
呢?
不如手玩一下(滑稽:
比如说(n=10),可以得到一个表格:
(i) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
(leftlfloordfrac{n}{i} ight floor) | 10 | 5 | 3 | 2 | 2 | 1 | 1 | 1 | 1 | 1 |
好了,容易发现取(1,2)两值的数有多个,考虑把他们一下子都筛掉。
我们考虑(leftlfloordfrac{n}{i}
ight
floor)的实际意义是:(i)这个数对应的取值,那么容易看出(leftlfloordfrac{n}{leftlfloordfrac{n}{i}
ight
floor}
ight
floor) 代表着为(leftlfloordfrac{n}{i}
ight
floor)值的最后一个数,我们就可以每发现一个新得数,直接跳就好了。
代码是这样的:
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.若(ileqslant sqrt{n}),那么(leftlfloordfrac{n}{i}
ight
floor) 的取值只有最多(sqrt{n})种。
2.若(i<sqrt{n}),那么(leftlfloordfrac{n}{i}
ight
floor) 的取值也只有最多(sqrt{n})种。
综上最多只有(2sqrt{n})种取值,复杂度就是(O(sqrt{n}))的。
当然有例题了,嘿嘿。
题目链接:P3935 Calculating
观察此题,发现(f(i))不就是裸的约数个数定理吗???
易得:
然而我们要求的是区间和。
设
直接求(G(r)-G(l-1))就行了。
我们考虑对(G(n))化简。
考虑每个数的贡献,每个数(x)作为别统计的约数出现了(leftlfloordfrac{n}{x}
ight
floor)次。
此题得到的式子为:
数论分块求即可。
(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
",(cal(r)-cal(l-1)+mod)%mod);
return 0;
}
当然,在问及数论分块的板题时,更多人会说这个:
题目链接:P2261 [CQOI2007]余数求和
我们知道取模的另一种意义:
那我们来化简式子吧:
然后数论分块就好了。
不过还是需要注意一些问题的:
这里数论分块是有界的数论分块,为什么呢,我们对式子进行进一步化简:
设原式为(S(n,k)),那么:
为什么呢,由于当(i>k)时,(left lfloor dfrac{k}{i}
ight
floor=0)。
对于(nleqslant 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
",sum-cal(k,min(n,k)));
return 0;
}
应该能看懂吧......