整除分块
适用范围
以现在所见,处理类似下取整求和的时候可以用到,时间复杂度一般为 \(O(\sqrt{n})\)。
例题 \(1\)
发现 \(\left\lfloor\dfrac{k}{i}\right\rfloor\) 至多只有 \(2 \times \sqrt{k}\) 种取值,证明如下:
当 \(i \le \sqrt{k}\) 时,因为 \(i\) 至多有 \(\sqrt{k}\) 种取值,故 \(\left\lfloor\dfrac{k}{i}\right\rfloor\) 至多有 \(\sqrt{k}\) 种取值;当 \(i > \sqrt{k}\) 时,因为 \(\left\lfloor\dfrac{k}{i}\right\rfloor \le \sqrt{k}\),故 \(\left\lfloor\dfrac{k}{i}\right\rfloor\) 至多有 \(\sqrt{k}\) 种取值。
综上,\(\left\lfloor\dfrac{k}{i}\right\rfloor\) 至多有 \(2\times \sqrt{k}\) 种取值,故可以枚举 \(\left\lfloor\dfrac{k}{i}\right\rfloor\),其对应的 \(i\) 一定是一段连续区间,找到左右端点就可以使用等差数列求和公式求解了。
由于新的值的左端点一定是上一个值的右端点加一,所以现在的问题转化为如何找到最大的 \(i\) 使得 \(\left\lfloor\dfrac{k}{i}\right\rfloor=\left\lfloor\dfrac{k}{l}\right\rfloor\)。
由于后者是已知的,将其设为 \(x\),问题变为求最大的 \(i\) 使得 \(i \times x \le k\),发现 \(i\) 可以取 \(\left\lfloor\dfrac{k}{x}\right\rfloor\),此时若 \(i\) 增加,则 \(i \times x > k\),不符合,故该值为最大的合法 \(i\)。
最后注意边界情况。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INT_MAX (int)(1e18)
int n,k,ans;
inline int read(){
int t=0,f=1;
register char c=getchar();
while(c<'0'||c>'9') f=(c=='-')?(-1):(f),c=getchar();
while(c>='0'&&c<='9') t=(t<<3)+(t<<1)+(c^48),c=getchar();
return t*f;
}
void solution(){
//sum_{i=1}^n k \mod i = sum_{i=1}^n k-floor(k/i)*i
//= sum_{i=1}^n k - sum_{i=1}^n floor(k/i)*i
//求出 max(i) 使得 floor(k/i)=p p为给定常数
//即求出 max(i) 使得 p*i<=k && p*(i+1)>k
//i=k/p
}
signed main(){
n=read(),k=read();
ans=n*k,n=min(n,k);
for(int l=1,r;l<=n;l=r+1){
if(k/l) r=min(k/(k/l),n);
else r=n;
ans-=(k/l)*(r-l+1)*(l+r)/2;
}
cout<<ans;
return 0;
}
例题 \(2\)
不妨假设 \(n>m\),可以先简单拆一下:
发现前者根据上一道题肯定是好求的,将 \(r=\left\lfloor\frac{n}{\left\lfloor\frac{n}{l}\right\rfloor}\right\rfloor\) 即可,但是后者同时有两个变量。
考虑找到一个区间 \((l,r)\) 使得区间中的数 \(j\) 满足 \(\left\lfloor\frac{n}{j}\right\rfloor\) 都相等,同时满足 \(\left\lfloor\frac{m}{j}\right\rfloor\) 都相等。
只有一个变量的时候我们相当于将一个数轴划分成了若干个区间,其中每个区间中的数都相等,那两个变量就只需要将两个数轴重合,发现此时每个小区间就是我们要求的东西。
由于原本每个数轴至多有 \(2 \times \sqrt{n}\) 个区间,合并之后就变成了 \(4 \times \sqrt{n}\) 个区间,可以接受。
这就相当于将 \(r=\min(\left\lfloor\frac{n}{\left\lfloor\frac{n}{l}\right\rfloor}\right\rfloor,\left\lfloor\frac{m}{\left\lfloor\frac{m}{l}\right\rfloor}\right\rfloor)\) 即可。
现在相当于给你一个区间 \((l,r)\),然后对一个东西求和。观察后面的式子,很明显第一个直接求,第二个使用等差数列求和求,第三个只需要使用平方和公式求即可。
注意求解逆元的时候该模数不是质数,先求出其的欧拉函数然后求逆元即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INT_MAX (int)(1e18)
const int mod=19940417;
int n,m,two_ni,six_ni,cnt;
inline int read(){
int t=0,f=1;
register char c=getchar();
while(c<'0'||c>'9') f=(c=='-')?(-1):(f),c=getchar();
while(c>='0'&&c<='9') t=(t<<3)+(t<<1)+(c^48),c=getchar();
return t*f;
}
int ksm(int x,int y){
int sum=1;
while(y){
if(y&1) sum=sum*x%mod;
x=x*x%mod,y>>=1;
}
return sum;
}
int calc(int x){
int res=x*x%mod;
for(int l=1,r;l<=x;l=r+1){
r=x/(x/l);
res=(res-(l+r)*(r-l+1)%mod*(x/l)%mod*two_ni%mod)%mod;
}
res=(res+mod)%mod;
return res;
}
int calc2(int x){
int res=x*(x+1)%mod*(2*x+1)%mod*six_ni%mod;
return res;
}
int calc1(){
int res=n*m%mod*m%mod;
for(int l=1,r;l<=m;l=r+1){
r=min(n/(n/l),m/(m/l));
// cout<<"l:"<<l<<" r:"<<r<<" "<<
res=(res+(calc2(r)-calc2(l-1))%mod*(n/l)%mod*(m/l)%mod)%mod;
res=(res-((n/l)*m%mod+(m/l)*n%mod)%mod*(l+r)%mod*(r-l+1)%mod*two_ni%mod)%mod;
}
res=(res+mod)%mod;
return res;
}
int Calc(){
int x=calc(n),y=calc(m);
int ans=x*y%mod-calc1();
ans=(ans%mod+mod)%mod;
return ans;
}
signed main(){
n=read(),m=read();
two_ni=ksm(2,17091780-1);
six_ni=ksm(6,17091780-1);
if(n<m) swap(n,m);
cout<<Calc()<<"\n";
return 0;
}

浙公网安备 33010602011771号