整除分块
适用范围
以现在所见,处理类似下取整求和的时候可以用到,时间复杂度一般为 \(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号