题解:[国家集训队] Crash的数字表格 / JZPTAB

题目传送门

莫比乌斯反演

对于这种题目,显然是一个数学题,因此可以转换。

不妨钦定 \(n\leq m\),否则交换 \(n,m\)

记答案为 \(\textit{ans}\)

\[\begin{aligned} \textit{ans}&=\sum_{i=1}^n\sum_{j=1}^m\operatorname{lcm}(i,j)\\ &=\sum_{i=1}^n\sum_{j=1}^m\dfrac{ij}{\gcd(i,j)}\\ &=\sum_{d=1}^n\sum_{i=1}^n\sum_{j=1}^m[\gcd(i,j)=d]\frac{ij}{d}\\ \end{aligned} \]

将枚举的 \(i,j\) 换为 \(i'd,j'd\),则 \(i'\perp j'\)

\[\textit{ans}=\sum_{d=1}^nd\sum_{i=1}^{\large\left\lfloor\frac nd\right\rfloor}\sum_{j=1}^{\large\left\lfloor\frac md\right\rfloor}[i\perp j]ij \]

考虑到:

\[[i\perp j]=[\gcd(i,j)=1]=\sum_{d\mid\gcd(i,j)}\mu(d)=\sum_{d\mid i\land d\mid j}\mu(d) \]

因此:

\[\textit{ans}=\sum_{d=1}^nd\sum_{i=1}^{\large\left\lfloor\frac nd\right\rfloor}\sum_{j=1}^{\large\left\lfloor\frac md\right\rfloor}\sum_{k\mid\gcd(i,j)}\mu(k)ij \]

我们可以枚举 \(k\)。因为 \(k\mid\gcd(i,j)\)\(i,j\) 的上界为 \(\left\lfloor\dfrac nd\right\rfloor\),因此从 \(1\sim\left\lfloor\dfrac nd\right\rfloor\) 枚举。

\[\textit{ans}=\sum_{d=1}^nd\sum_{k=1}^{\large\left\lfloor\frac nd\right\rfloor}\mu(k)\sum_{i=1}^{\large\left\lfloor\frac nd\right\rfloor}\sum_{j=1}^{\large\left\lfloor\frac md\right\rfloor}[k\mid\gcd(i,j)]ij \]

\(k\mid\gcd(i,j)\)\(k\mid i\land k\mid j\),因此可以枚举 \(i=i'l,j=j'k\)

\[\begin{aligned} \textit{ans}&=\sum_{d=1}^nd\sum_{k=1}^{\large\left\lfloor\frac nd\right\rfloor}\mu(k)k^2\sum_{i=1}^{\large\left\lfloor\frac n{kd}\right\rfloor}\sum_{j=1}^{\large\left\lfloor\frac m{kd}\right\rfloor}ij\\ &=\sum_{d=1}^nd\sum_{k=1}^{\large\left\lfloor\frac nd\right\rfloor}\mu(k)k^2\left(\sum_{i=1}^{\large\left\lfloor\frac n{kd}\right\rfloor}i\right)\left(\sum_{j=1}^{\large\left\lfloor\frac m{kd}\right\rfloor}j\right) \end{aligned} \]

枚举 \(d\)\(\mathcal O(n)\) 的,考虑快速求出后半部分。

\(k,d\) 确定,则可以由等差数列求和快速求出 \(\sum_{i=1}\limits^{\large\left\lfloor\frac n{kd}\right\rfloor}i,\sum_{j=1}\limits^{\large\left\lfloor\frac n{kd}\right\rfloor}j\)

可以考虑进行数论分块,则 \(k\) 在一段区间 \([l,r]\) 内,\(\left\lfloor\dfrac n{kd}\right\rfloor\) 均相同,\(\left\lfloor\dfrac n{kd}\right\rfloor\) 均相同。于是后两项就可以确定。对于 \(\mu(k)k^2\),线性筛后维护一个前缀和即可。

AC 代码

//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
typedef long long ll;
#define int long long
constexpr const int N=1e7,P=20101009,inv4=15075757;
int preU[N+1]; 
void pre(){
	static int vis[N+1],prime[N+1],size;
	preU[1]=1;
	for(int i=2;i<=N;i++){
		if(!vis[i]){
			prime[++size]=i;
			vis[i]=i;
			preU[i]=-1;
		}
		for(int j=1;j<=size&&i*prime[j]<=N;j++){
			vis[i*prime[j]]=prime[j];
			if(i%prime[j]==0){
				break;
			}
			preU[i*prime[j]]=-preU[i];
		}
	}
	for(int i=1;i<=N;i++){
		preU[i]=1ll*preU[i]*i*i%P;
		preU[i]+=preU[i-1];
		preU[i]%=P;
	}
}
ll f(int n,int m){
	if(n>m){
		swap(n,m);
	}
//	for(int i=1;i<=n;i++){
//		cerr<<"u["<<i<<"]="<<preU[i]-preU[i-1]-i*i<<endl;
//		cerr<<"u["<<i<<"]+"<<i<<"^2="<<preU[i]-preU[i-1]<<endl;
//		cerr<<"preU["<<i<<"]="<<preU[i]<<endl;
//	}
	int ans=0;
	for(int d=1;d<=n;d++){
		int n2=n/d,m2=m/d;
//		cerr<<"-------------------------------\nd="<<d<<":\n";
//		cerr<<"floor(n/d)="<<n2<<" floor(m/d)="<<m2<<endl;
		int ans2=ans;
        for(int l=1,r=n2;l<=n2;l=r+1,r=n2){
        	int tn=n2/l,tm=m2/l;
//        	cerr<<"tn="<<tn<<" tm="<<tm<<endl;
        	r=min(n2/tn,m2/tm);
//        	cerr<<"["<<l<<","<<r<<"]\n";
//        	cerr<<"preU[r]="<<preU[r]<<" preU[l-1]="<<preU[l-1]<<endl; 
//        	cerr<<"add = "<<1ll*d*(preU[r]-preU[l-1])*inv4%P*tn*(tn+1)%P*tm*(tm+1)%P<<endl;
        	ans+=1ll*d*(preU[r]-preU[l-1])%P*inv4%P*tn%P*(tn+1)%P*tm%P*(tm+1)%P;
        	ans%=P;
		}
//		cerr<<ans-ans2<<endl;
		
//		int add=0;
//		for(int k=1;k*d<=n;k++){
//			int nn=n/k/d,mm=m/k/d;
//			add+=1ll*d*(preU[k]-preU[k-1]-k*k)/*u[k]*/*k%P*k%P*(1+nn)*nn%P%P*(1+mm)*mm%P*inv4%P;
//			add%=P;
//		}
//		cout<<"add in all should be "<<add<<endl;
		
	}
	if(ans<0){
		ans+=P;
	}
	return ans;
}
main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	pre();
	int n,m;
	cin>>n>>m;
	cout<<f(n,m)<<'\n';
	
	cout.flush();
	 
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}
posted @ 2025-07-22 18:07  TH911  阅读(14)  评论(0)    收藏  举报