专栏文章
整除分块学习笔记
算法·理论参与者 4已保存评论 6
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 6 条
- 当前快照
- 1 份
- 快照标识符
- @mioxwms4
- 此快照首次捕获于
- 2025/12/03 02:57 3 个月前
- 此快照最后确认于
- 2025/12/03 02:57 3 个月前
0.前言
本文为作者放假前の无聊之举……
作者是个菜*,若文章有错误请大佬指正。
食用本文的注意事项如下:
- 粗体字为重要结论或重要步骤。
- 给到的例题为作者抽象出来的模型,与原题不同。若作者找到了原题,作者会把原题链接放在下面,方便大家提交。在每一道题的讲解中,原题将称作“原题”,例题将称作“例题”。
- 若有原题,那么给出的代码为原题的ac代码,若没有原题,给出的代码为例题代码。
- 划掉的部分为同学评价
诋毁或作者突发恶疾,请自动忽略。 - 文章中还有一些作者未解决的问题,这样的问题后面会有“请求大佬指点”,若有大佬知道,请在评论区回复。作者会在第一时间送上最高礼节:关注。
1.整除分块の基本问题
求下面式子的值
这是原题链接(以下此题称基本问题)
暴力很简单,就不给代码了。
这里n是 级别的, 肯定会被卡掉,要求给出一个 的算法。
这里我们请出整除分块来解决。
这里n是 级别的, 肯定会被卡掉,要求给出一个 的算法。
这里我们请出整除分块来解决。
2.基本思想&基本定理
我们手算一个原题中的样例
- n = 10
- i = 1 2 3 4 5 6 7 8 9 10
- = 10 5 3 2 2 1 1 1 1 1
通过样例,我们可以看见当i从1到n变化时, 的值在很多连续的i区间内是相同的,有了这个关键性质,我们就可以优化了。
整除分块就是找到这些区间,对每个区间只计算一次的值,然后乘以区间长度。
下面我们来讲讲这个算法的步骤
- 初始化i=1,结果ans=0;
- 对于每个i,计算;
- 找到最大的j使得(也就是块的右端点)。这里,我们隆重请出我们的基本定理:对于 , 的值会随着i的增大而单调递减,且某些连续区间内的值相同。即,其中 。
然而作者并不会证明; - 将 加到结果中;
- 令 ,重复步骤2~5直到 ;
那么很好,通过我们强大的基本定理,我们的复杂度从。
下面是大家喜闻乐见的代码环节:
CPP#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll T,n;
ll getsum(ll x)
{
ll ans=0;
for (ll i=1,j;i<=x;i=j+1)
{
j=x/(x/i);
ans+=x/i*(j-i+1);
}
return ans;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>T;
while(T--)
{
cin>>n;
cout<<getsum(n)<<"\n";
}
return 0;
}
3.常见题型
1.例题1
求出下面式子的值:
观察这个式子,发现只比基本问题多乘一个i。那么我们手推一下样例:
- n = 10
- i = 1 2 3 4 5 6 7 8 9 10
- =
- sum = 87
通过以上样例我们可以看出,每一个块中都是一个等差数列。 令,即基本问题中每一个块中相等的值,其中:
- 首项 = ;
- 令,即基本问题中块的最后一项,则末项 = ;
- 项数很简单,是;
- 最后,每一个块中的;
那么,又到了我们喜闻乐见的代码环节:其实上面的代码改一下就可以了
CPP#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll T,n;
ll getsum(ll x)
{
ll ans=0;
for (ll i=1,j;i<=x;i=j+1)
{
ll q=x/i;//改了这里
j=x/q;
ans+=q*(i+j)*(j-i+1)/2;//还有这里
}
return ans;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>T;
while(T--)
{
cin>>n;
cout<<getsum(n)<<"\n";
}
return 0;
}
2.例题2
求下面的式子:
如果看出这道题是整除分块,就非常好ban了。那就往整除分块的基本问题上靠,也就是写出带有整除的式子:
把 写为 ,代入可得
我们看到了例题1的形式,把求和符号写开,就可以转化为,例1了:
这样这道题就做完了,和例1基本类似,不过有一点小问题需要注意一下,k可能小于n,所以 可能超过n。(可以手动验证一下)
所以呢,代码要进行一些小小的改动。
所以呢,代码要进行一些小小的改动。
又是我们喜闻乐见的代码环节呢~
CPP#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,k,l,r,ans;
int main()
{
cin>>n>>k;
ans=n*k;
for (int l=1;l<=n;l=r+1)
{
if (k/l==0) break;
r=min(k/(k/l),n);// 改下这里
ans-=(k/l)*(l+r)*(r-l+1)/2;
}
printf("%lld\n",ans);
return 0;
}
3.例题3
求下面式子的值
其中,函数d(x)表示正整数x因子个数。
这里我对原题题面进行了化简,原题中的函数f与本题的函数d的功能是一样的。为什么呢,这里简单证明一下:
我们发现,对于正整数 的任意因数,一定由 的质因数组成。发现这个性质后,我们把就可以把因子个数计数问题转化为一个排列组合问题:在所有 的质因数中选择出一个由质因数组成的不同可重集合(也可以为空集)的方案数,集合中的元素元素相乘就是组成的因数。根据我们小学二年级学过的排列组合知识,我们发现,每一个 的质因数 ,有两种选择,即选和不选,选有 ( 的指数)种方案,根据加法原理,总共有 中方案,再根据乘法原理,总方案数为
与下文中的 区分开,上式中的 即为原题中的,即质因子种类数。
回归正题,我们发现函数 可以写成如下形式
那么代入原式可得
然后我们交换一下求和顺序,将固定i,找 改为固定d,找 且 ,
即
我们发现,第2个求和的意思是所有小于等于n的d的所有的倍数的个数,也就是 。
代入原式,即
代入原式,即
再令 ,可得到
我们欣喜地发现,这就是我们的基本问题ya.
我们进行了以上推导,这道题已经完成了大部分,不过若要通过此题,还有一些小细节要注意,我会在代码的注释部分写出。
CPP#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
ll l,r;
ll sum1,sum2;
ll getsum(ll x)
{
ll ans=0;
for (ll i=1,j;i<=x;i=j+1)
{
j=x/(x/i);
ans=(ans+((x/i)%mod*(j-i+1)%mod)%mod)%mod;
}
return ans%mod;
}
int main()
{
ios::sync_with_stdio(0);
cin>>l>>r;
sum1=getsum(l-1),sum2=getsum(r);//细节1:前缀思想
//cout<<sum1<<"\n";
//cout<<sum2<<"\n";
cout<<((sum2-sum1)%mod+mod)%mod;//细节2:取余。
//这个我看了讨论区的提示,我把链接放在下面↓
return 0;
}
这道题虽然结束了,但我还想说点废话。
其实我一开始并不是这么想的。
当我看到d(x)时,我想到
其实我一开始并不是这么想的。
当我看到d(x)时,我想到
其中, 表示x的欧拉函数值。
代入原式可得
代入原式可得
然后我们把求和符号写开
第一个求和是 的,但连续的欧拉函数求和能 求解吗?不清楚哎……
感谢大佬KingGojianOfYue为作者指点迷津。
看到这里你应该已经发现了我的错误,第一步就错了。欧拉函数的定义是小于 ,且与 互质的数的个数,则 表示小于 ,且不与 互质的数的个数,显然不等于 的因数个数。
4.例题4
求下面式子的值
有了上一道题的启发,这一道题的思路也很容易得出。
第一步和上题一样:
第一步和上题一样:
然后再代入原式,可得:
和上题一样,交换求和顺序,得出下面的式子:上面详细讲了,这里简单写了
然后我们惊喜的发现,这就是例题1的那个式子,然后我们就可以愉快的写代码了。
愉快の代码时刻↓
和上一道例题的原题一样,这道题也需要前缀思想,只是不用取余了。也许双倍经验?
CPP和上一道例题的原题一样,这道题也需要前缀思想,只是不用取余了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll x,y;
ll getsum(ll n)
{
ll ans=0;
for (ll i=1,j;i<=n;i=j+1)
{
int q=n/i;
j=n/q;
ans+=q*(i+j)*(j-i+1)/2;
}
return ans;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>x>>y;
cout<<getsum(y)-getsum(x-1);
return 0;
}
5.例题5
经常莫比乌斯反演的朋友都知道,在莫比乌斯反演中,经常需要计算形如下面的式子
其中 表示x的莫比乌斯函数的函数值。这道题我没有找到纯原题,但是在莫反中很常用。
对于0基础的小盆友,我贴心的把关于莫比乌斯函数的 前置知识 放在下面,大佬可自动跳过。为了大佬跳过,于是有了标题。
1.莫比乌斯函数定义
莫比乌斯函数的定义如下↓
(以上公式摘自deepseek)
没看懂的话我来详细说说后两个:
把x写成算数基本定理的形式
把x写成算数基本定理的形式
如果所有c都是1,那么是情况2;
如果存在c不是1,那么是情况3。
如果存在c不是1,那么是情况3。
2.莫比乌斯函数性质
没什么好说的,直接给式子
3.对原式分块
通过我们的细心观察,我们发现 一段区间 ,使得 和的值保持不变。
然后我们讲讲算法步骤:
- 对于块的左端点i,计算 。
- 算出块的右端点j。 。
- 求出每一个块的和。也就是 。
- 然后将和加到结果里。
下面又是喜闻乐见的代码环节呢~
由于这篇文章主要介绍整除分块,对于线性筛求出莫比乌斯函数值的前缀和代码中省略不写。
CPP由于这篇文章主要介绍整除分块,对于线性筛求出莫比乌斯函数值的前缀和代码中省略不写。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+6;
int s[maxn];//莫比乌斯函数的前缀和
ll n,m;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
ll ans=0;
ll q=min(n,m);
for (ll i=1,j;i<=q;i=j+1)
{
ll qn=n/i;
ll qm=m/i;
j=min(min(n/qn,m/qm),q);
ans+=(s[i]-s[j-1])*qn*qm;
}
cout<<ans;
return 0;
}
4.写在最后……
AI会帮你的——hsy
有一些确实是借助AI写的,不过观众又发现不了
本文主要是整除分块的基础内容,整除分块还有很多有趣的内容,我也许还会继续更新。
本文主要是整除分块的基础内容,整除分块还有很多有趣的内容,我也许还会继续更新。
最后郑重声明:尽管很简单,但作者假期前赶稿,难免会有错误,有大佬发现了请在评论区指出,我会献上最高礼节的。
第一次写文章,管理大大辛苦了,求通过。
最后的最后还有更新日志
快下课了,我明天接着更……// update on 7.9 离暑假还有3天。完成了文章的主体内容并写了第一道例题。
写了一会,我要去做题// update on 7.10 离暑假还有2天。做题任务还没完成,写了第第二道例题。
写了一下午,快吃饭了。// update on 7.11 距离暑假的时间已经可以用小时计算了,距离暑假还有21小时。爆肝了两道的例题,并修改了前面的部分内容。
刷了一晚上水贴,又聊了一会儿天 // 还是7.11 不过距离放假还有18小时,今天周杰伦是不是开演唱会?算了不能再玩了,我要去做题了,可是我要放假。写完了最后一道例题和结尾。
我要放假 // update on 7.12 离放假还有最后1小时。最后审查一遍,并增加部分细节。
// 2025.11.27 时间过得好快呀,更正了部分已知错误,解决了原来没有解决的问题,求管理大大快速通过。
相关推荐
评论
共 6 条评论,欢迎与作者交流。
正在加载评论...