数论推式子题选讲
sonny2011
·
·
算法·理论
A Luogu P1891
题目
求
\sum_{i = 1}^{n}{\operatorname{lcm}(i,n)}
### Solution
直接推式子
$$
\begin{align*}
&\sum_{i = 1}^{n}{\operatorname{lcm}(i,n)} \\
&= \sum_{i = 1}^{n}{in \gcd(i,n)^{-1}} \\
&= n \sum_{d | n}{d^{-1} \sum_{i = 1}^{n}{i [\gcd(i,n) = d]}} \\
&= n \sum_{d | n}{d^{-1} \sum_{i = 1}^{\frac{n}{d}}{id [gcd(i,\frac{n}{d}) = 1]}} \\
&= n \sum_{d | n}{\sum_{i = 1}^{\frac{n}{d}}{i [\gcd(i,\frac{n}{d}) = 1]}}
\end{align*}
$$
此时我们发现只有第二个和式比较难处理,我们把它拿出来单独处理。
设 $f(x) = \sum_{i = 1}^{x}{i [\gcd(i,x) = 1]}$,我们只要求出 $f$,就能做出来了。这个式子是小于等于 $x$ 中所有与 $x$ 互质的数的和,首先,小于等于 $x$ 且与它互质的数量是好求的,是 $\varphi(x)$,根据欧几里得算法,$\gcd(x,i) = \gcd(x,x - i)$,那么如果一个数 $i$ 和 $x$ 互质,那么 $x - i$ 也与 $x$ 互质,于是我们把算进答案里的这些数反过来再写一遍,那么就成了 $\varphi(x)$ 个 $x$ 的和,再除以 $2$ 就行了。
于是,我们得到了 $f$ 的表达式,$f(x) = \frac{\varphi(x) x}{2}$,带入原式
$$
Ans = n \sum_{d | n}{\frac{\varphi(\frac{n}{d}) \frac{n}{d}}{2}} \\
= n \sum_{d | n}{\frac{\varphi(d) d}{2}}
$$
欧拉筛预处理欧拉函数枚举因数计算答案即可。
时间复杂度 $O(n + T \sqrt{n})$。
### AC Code
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define uni unsigned int
#define ull unsigned long long
#define lll __int128
#define ld long double
#define pb push_back
#define mk make_pair
#define ppcnt __builtin_popcount
#define sort stable_sort
ll read()
{
ll k=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')k=k*10+c-'0',c=getchar();
return k*f;
}
void write(char c){putchar(c);}
int St[100],Top;
void write(int x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
void write(ll x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
int prime[1000010],cnt=0,phi[1000010];
void init()
{
phi[1]=1;
for(int i=2;i<=1000000;++i)
{
if(!phi[i])
{
phi[i]=i-1;
prime[++cnt]=i;
}
for(int j=1;j<=cnt&&i*prime[j]<=1000000;++j)
{
phi[i*prime[j]]=phi[i]*phi[prime[j]];
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
}
}
}
void solve()
{
int n=read();
ll ans=0;
for(int i=1;i*i<=n;++i)
{
if(n%i==0)
{
ans+=(1ll*phi[i]*i+1)>>1;
if(i*i!=n)ans+=(1ll*phi[n/i]*(n/i)+1)>>1;
}
}
ans*=n;
write(ans),write('\n');
}
int main()
{
std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
init();
int T=read();
while(T--)solve();
return 0;
}
```
## [B Luogu P2257](https://www.luogu.com.cn/problem/P2257)
### 题目
求
$$
\sum_{i = 1}^{n}{\sum_{j = 1}^{m}{[\gcd(i,j) \text{是质数}]}}
$$
$T \le 10^4 , n , m \le 10^7$。
### Solution
推式子,令 $n le m$,下面会用 $\in prime$ 表示这个数是质数,用 $p$ 代表只枚举满足条件的质数
$$
\begin{align*}
&\sum_{i = 1}^{n}{\sum_{j = 1}^{m}{[\gcd(i,j) \in prime]}} \\
&= \sum_{p}^{n}{\sum_{i = 1}^{n}{\sum_{j = 1}^{m}{[\gcd(i,j) = p]}}} \\
&= \sum_{p}^{n}{\sum_{i = 1}^{\lfloor \frac{n}{p} \rfloor}{\sum_{j = 1}^{\lfloor \frac{m}{p} \rfloor}{[\gcd(i,j) = 1]}}} \\
&= \sum_{p}^{n}{\sum_{i = 1}^{\lfloor \frac{n}{p} \rfloor}{\sum_{j = 1}^{\lfloor \frac{m}{p} \rfloor}{\sum_{k | i , k | j}{\mu(k)}}}} \\
&= \sum_{p}^{n}{\sum_{k = 1}^{\lfloor \frac{n}{p} \rfloor}{\mu(k) \lfloor \frac{n}{pk} \rfloor \lfloor \frac{m}{pk} \rfloor}}
\end{align*}
$$
此时考虑令 $t = pk$,枚举 $t
\begin{align*}
&Ans = \sum_{t = 1}^{n}{\lfloor \frac{n}{t} \rfloor \lfloor \frac{m}{t} \rfloor \sum_{p | t}{\mu(\frac{t}{p})}}
\end{align*}
此时后面的和式已经和前面无关了,可以预处理,前面的是一个下取整式,于是直接二维整除分块即可。
时间复杂度 O(n \log \log n + T \sqrt{n})。
AC Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define uni unsigned int
#define ull unsigned long long
#define lll __int128
#define ld long double
#define pb push_back
#define mk make_pair
#define ppcnt __builtin_popcount
#define sort stable_sort
ll read()
{
ll k=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')k=k*10+c-'0',c=getchar();
return k*f;
}
void write(char c){putchar(c);}
int St[100],Top;
void write(int x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
void write(ll x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
int prime[5000010],cnt=0;
ll mu[10000010],f[10000010];
bitset<10000010> vis;
void init()
{
mu[1]=1;
for(int i=2;i<=10000000;++i)
{
if(!vis[i])
{
prime[++cnt]=i;
mu[i]=-1;
}
for(int j=1;j<=cnt&&i*prime[j]<=10000000;++j)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<=cnt;++i)for(int j=prime[i];j<=10000000;j+=prime[i])f[j]+=mu[j/prime[i]];
for(int i=1;i<=10000000;++i)f[i]+=f[i-1];
}
void solve()
{
ll n=read(),m=read(),ans=0;
if(n>m)swap(n,m);
for(ll l=1,r;l<=n;l=r+1)
{
r=min(n/(n/l),m/(m/l));
ans+=(f[r]-f[l-1])*(n/l)*(m/l);
}
write(ans),write('\n');
}
int main()
{
std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
init();
int T=read();
while(T--)solve();
return 0;
}
C Luogu P2260
题目
求
\sum_{i = 1}^{n}{\sum_{j = 1}^{m}{(n \bmod i) \times (m \bmod j) , i \neq j}}
### Solution
推式子,令 $n \le m
\begin{align*}
&\sum_{i = 1}^{n}{\sum_{j = 1}^{m}{(n \bmod i) \cdot (m \bmod j) , i \neq j}} \\
&= \sum_{i = 1}^{n}{\sum_{j = 1}^{m}{(n \bmod i) \cdot (m \bmod j)}} - \sum_{i = 1}^{n}{(n \bmod i) \cdot (m \bmod i)}
\end{align*}
先算前一部分
\begin{align*}
&\sum_{i = 1}^{n}{\sum_{j = 1}^{m}{(n \bmod i) \cdot (m \bmod j)}} \\
&= (\sum_{i = 1}^{n}{n \bmod i})(\sum_{i = 1}^{m}{m \bmod i})\\
\end{align*}
设 f(x) = \sum_{i = 1}^{x}{x \bmod i},考虑求 f 的表达式
\begin{align*}
&\sum_{i = 1}^{x}{x \bmod i} \\
&= \sum_{i = 1}^{x}{x - i \lfloor \frac{x}{i} \rfloor} \\
&= x^2 - \sum_{i = 1}^{x}{\lfloor \frac{x}{i} \rfloor}
\end{align*}
后面的和式直接数论分块就能算,于是前面的式子就处理完了,再处理后面的
\begin{align*}
&\sum_{i = 1}^{n}{(n \bmod i) \cdot (m \bmod i)} \\
&= \sum_{i = 1}^{n}{(n - i \lfloor \frac{n}{i} \rfloor) \cdot (m - i \lfloor \frac{m}{i} \rfloor)} \\
&= \sum_{i = 1}^{n}{nm - ni \lfloor \frac{m}{i} \rfloor - mi \lfloor \frac{n}{i} \rfloor + i^2 \lfloor \frac{n}{i} \rfloor \lfloor \frac{m}{i} \rfloor} \\
&= n^2 m - n \sum_{i = 1}^{n}{i \lfloor \frac{m}{i} \rfloor} - m \sum_{i = 1}^{n}{i \lfloor \frac{n}{i} \rfloor} + \sum_{i = 1}^{n}{i^2 \lfloor \frac{n}{i} \rfloor \lfloor \frac{m}{i} \rfloor}
\end{align*}
对每个和式分别写一个数论分块即可。
时间复杂度 O(\sqrt{n} + \sqrt{m})。
AC Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define uni unsigned int
#define ull unsigned long long
#define lll __int128
#define ld long double
#define pb push_back
#define mk make_pair
#define ppcnt __builtin_popcount
#define sort stable_sort
const ll mod=19940417;
ll read()
{
ll k=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')k=k*10+c-'0',c=getchar();
return k*f;
}
void write(char c){putchar(c);}
int St[100],Top;
void write(int x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
void write(ll x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
int main()
{
std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
ll n=read(),m=read();
if(n>m)swap(n,m);
ll Ans=n*n%mod*m%mod*m%mod-n*n%mod*m%mod;
Ans%=mod;
ll ansn1=0,ansm1=0;
for(ll l=1,r;l<=n;l=r+1)
{
r=(n/(n/l));
(ansn1+=(r*(r+1)/2-l*(l-1)/2)%mod*(n/l)%mod)%=mod;
}
for(ll l=1,r;l<=m;l=r+1)
{
r=(m/(m/l));
(ansm1+=(r*(r+1)/2-l*(l-1)/2)%mod*(m/l)%mod)%=mod;
}
(Ans+=ansn1*ansm1%mod)%=mod;
(Ans-=n*n%mod*ansm1%mod)%=mod;
(Ans-=(m*m%mod-m)*ansn1%mod)%=mod;
ansm1=0;
for(ll l=1,r;l<=n;l=r+1)
{
r=min(n,(m/(m/l)));
(ansm1+=(r*(r+1)/2-l*(l-1)/2)%mod*(m/l)%mod)%=mod;
}
(Ans+=n*ansm1%mod)%=mod;
ll ans=0;
for(ll l=1,r;l<=n;l=r+1)
{
r=min({n,n/(n/l),m/(m/l)});
ll sum=((lll)r*(r+1)*(2*r+1)/6%mod)-((lll)(l-1)*(l)*(2*l-1)/6%mod);
ans+=(n/l)*(m/l)%mod*sum%mod;
}
(Ans-=ans)%=mod;
write((Ans+mod)%mod);
return 0;
}
D Luogu P2303
题目
求
\sum_{i = 1}^{n}{\gcd(i,n)}
### Solution
推式子基本功
$$
\begin{align*}
&\sum_{i = 1}^{n}{\gcd(i,n)} \\
&= \sum_{d | n}{d \sum_{i = 1}^{n}{[\gcd(i,n) = d]}} \\
&= \sum_{d | n}{d \sum_{i = 1}^{\frac{n}{d}}{[\gcd(i,\frac{n}{d}) = 1]}} \\
&= \sum_{d | n}{d \varphi(\frac{n}{d})} \\
&= \sum_{d | n}{\frac{n}{d} \varphi(d)} \\
&= \sum_{d | n}{\frac{n}{d} d \prod_{p | d}{\frac{p - 1}{p}}} \\
&= n \sum_{d | n}{\prod_{p | d}{\frac{p - 1}{p}}}
\end{align*}
$$
考虑对这个式子因式分解,写成一堆乘积的形式,于是我们发现,对某个质数 $p$,把它提出来后,除了它以外的东西应该是一样的,它的贡献应该是若干个选他的贡献和一个不选它的贡献,选的贡献应该是若干个 $\frac{p - 1}{p}$ 的和,不选的应该是 $1$。那么它贡献的次数是多少呢,考虑 $n$ 的质因数分解,$p$ 的贡献次数应该是 $n_p$,那么它的总贡献应该是 $n_p \frac{p - 1}{p} + 1$。
带回原式,得到
$$
\begin{align*}
&Ans = \prod_{p | n}{n_p \frac{p - 1}{p} + 1} \\
&=\prod_{p | n}{\frac{n_p (p - 1) + p}{p}} \\
&=\prod_{p | n}{\frac{n_p p - n_p + p}{p}}
\end{align*}
$$
时间复杂度 $O(\sqrt{n})$。
### AC Code
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define uni unsigned int
#define ull unsigned long long
#define lll __int128
#define ld long double
#define pb push_back
#define mk make_pair
#define ppcnt __builtin_popcount
#define sort stable_sort
ll read()
{
ll k=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')k=k*10+c-'0',c=getchar();
return k*f;
}
void write(char c){putchar(c);}
int St[100],Top;
void write(int x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
void write(ll x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
int main()
{
std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
ll n=read();
ll ans=n;
for(ll i=2;i*i<=n;++i)
{
if(n%i==0)
{
ll cnt=0;
while(n%i==0)n/=i,cnt++;
ans/=i;
ans*=cnt*i-cnt+i;
}
}
if(n>1)
{
ans/=n;
ans*=n*2-1;
}
write(ans);
return 0;
}
```
## [E Luogu P3312](https://www.luogu.com.cn/problem/P3312)
### 题目
有一张 $n \times m$ 的数表,其第 $i$ 行第 $j$ 列($1\le i\le n$,$1\le j\le m$)的数值为能同时整除 $i$ 和 $j$ 的所有自然数之和。给定 $a$,计算数表中不大于 $a$ 的数之和。
$n , m \le 10^5 , q \le 2 \times 10^4$。
### Solution
首先,我们能发现,每个格子里的数字应该是 $\gcd(i,j)$ 的因数和函数的值 $\sigma(\gcd(i,j))$。
然后,我们不管 $a$ 的限制,令 $n \le m$,开始推式子
$$
\begin{align*}
&\sum_{i = 1}^{n}{\sum_{j = 1}^{m}{\sigma(\gcd(i,j))}} \\
&= \sum_{d = 1}^{n}{\sigma(d) \sum_{i = 1}^{n}{\sum_{j = 1}^{m}{[\gcd(i,j) = d]}}} \\
&= \sum_{d = 1}^{n}{\sigma(d) \sum_{i = 1}^{\lfloor \frac{n}{d} \rfloor}{\sum_{j = 1}^{\lfloor \frac{m}{d} \rfloor}{[\gcd(i,j) = 1]}}} \\
&= \sum_{d = 1}^{n}{\sigma(d) \sum_{i = 1}^{\lfloor \frac{n}{d} \rfloor}{\sum_{j = 1}^{\lfloor \frac{m}{d} \rfloor}{\sum_{k | i , k | j}{\mu(k)}}}} \\
&= \sum_{d = 1}^{n}{\sigma(d) \sum_{k = 1}^{\lfloor \frac{n}{d} \rfloor}{\mu(k) \lfloor \frac{n}{dk} \rfloor \lfloor \frac{m}{dk} \rfloor}} \\
&= \sum_{t = 1}^{n}{\lfloor \frac{n}{t} \rfloor \lfloor \frac{m}{t} \rfloor \sum_{d | t}{\sigma(d) \mu(\frac{t}{d})}}
\end{align*}
$$
这个时候已经可以数论分块做了。
下面来考虑 $a$ 的限制。我们发现后面的这个和式是跟 $\sigma(d)$ 有关的,那么代表着只有在 $\sigma(d)$ 小于等于 $a$ 的时候才会对这个和式产生贡献,那么我们考虑离线所有询问,按 $a$ 排序,用一种数据结构维护单点改和区间和,每次询问前把新增的会产生贡献的数加进来。
时间复杂度 $O(n \ln n + q \sqrt{n})$。
### AC Code
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define uni unsigned int
#define ull unsigned long long
#define lll __int128
#define ld long double
#define pb push_back
#define mk make_pair
#define ppcnt __builtin_popcount
#define sort stable_sort
const ll mod=(1ll<<31);
ll read()
{
ll k=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')k=k*10+c-'0',c=getchar();
return k*f;
}
void write(char c){putchar(c);}
int St[100],Top;
void write(int x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
void write(ll x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
int prime[100010],cnt=0;
ll f[100010],g[100010],mu[100010];
bitset<100000> vis;
vector<pair<ll,ll>> vec;
void init()
{
f[1]=g[1]=mu[1]=1;
for(int i=2;i<=100000;++i)
{
if(!vis[i])
{
prime[++cnt]=i;
f[i]=g[i]=i+1;
mu[i]=-1;
}
for(int j=1;j<=cnt&&i*prime[j]<=100000;++j)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
g[i*prime[j]]=g[i]*prime[j]+1;
f[i*prime[j]]=f[i]/g[i]*g[i*prime[j]];
break;
}
g[i*prime[j]]=prime[j]+1;
f[i*prime[j]]=f[i]*g[i*prime[j]];
mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<=100000;++i)vec.pb(mk(f[i],i));
}
struct Query
{
ll n,m,a;
int id;
}b[20010];
ll t[100010];
int lowbit(int x){return x&(-x);}
void updata(int x,ll d)
{
while(x&&x<=100000)
{
t[x]+=d;
x+=lowbit(x);
}
}
ll query(int x)
{
ll res=0;
while(x)
{
res+=t[x];
x-=lowbit(x);
}
return res;
}
ll ans[20010];
int main()
{
std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
init();
sort(vec.begin(),vec.end());
int q=read();
for(int i=1;i<=q;++i)
{
b[i].n=read(),b[i].m=read(),b[i].a=read();
if(b[i].n>b[i].m)swap(b[i].n,b[i].m);
b[i].id=i;
}
sort(b+1,b+1+q,[](Query x,Query y){return x.a<y.a;});
for(int i=1,j=0;i<=q;++i)
{
while(j<100000&&vec[j].first<=b[i].a)
{
for(int k=vec[j].second;k<=100000;k+=vec[j].second)
{
updata(k,vec[j].first*mu[k/vec[j].second]);
}
++j;
}
ll n=b[i].n,m=b[i].m,res=0;
for(ll l=1,r;l<=n;l=r+1)
{
r=min({n,n/(n/l),m/(m/l)});
res+=(query(r)-query(l-1))*(n/l)%mod*(m/l)%mod;
}
ans[b[i].id]=res;
}
for(int i=1;i<=q;++i)write(ans[i]%mod),write('\n');
return 0;
}
```
## [F Luogu P3327](https://www.luogu.com.cn/problem/P3327)
### 题目
设 $d(x)$ 为 $x$ 的约数的个数,求
$$
\sum_{i = 1}^{n}{\sum_{j = 1}^{m}{d(ij)}}
$$
$T , n , m \le 5 \times 10^4$。
### Solution
首先这个题需要一个非常奇妙的转化
$$
d(ij) = \sum_{x | i}{\sum_{y | j}{[\gcd(x,y) = 1]}}
$$
显然对于每个质因子的贡献是独立的,于是,只需要考虑每个质因子的贡献即可。
对于某个质因子 $p$,它在 $i$ 中的出现次数是 $i_p$,在 $j$ 中的出现次数是 $j_p$,那么它在 $ij$ 的出现次数是 $i_p + j_p$,那它的贡献是 $i_p + j_p + 1$,而它满足 $\gcd(x,y) = 1$,就是它只能在某一个中出现,简单容斥得到贡献为 $i_p + j_p + 1$。
然后大力推式子
$$
\begin{align*}
&\sum_{i = 1}^{n}{\sum_{j = 1}^{m}{d(ij)}} \\
&= \sum_{i = 1}^{n}{\sum_{j = 1}^{m}{\sum_{x | i}{\sum_{y | j}{[\gcd(x,y) = 1]}}}} \\
&= \sum_{x = 1}^{n}{\sum_{y = 1}^{m}{[\gcd(x,y) = 1] \lfloor \frac{n}{x} \rfloor \lfloor \frac{m}{y} \rfloor}} \\
&= \sum_{x = 1}^{n}{\sum_{y = 1}^{m}{\lfloor \frac{n}{x} \rfloor \lfloor \frac{m}{y} \rfloor \sum_{k | x , k | y}{\mu(k)}}} \\
&= \sum_{k = 1}^{n}{\mu(k) \sum_{x = 1}^{\lfloor \frac{n}{k} \rfloor}{\sum_{y = 1}^{\lfloor \frac{m}{k} \rfloor}{\lfloor \frac{n}{xk} \rfloor \lfloor \frac{m}{yk} \rfloor}}} \\
&= \sum_{k = 1}^{n}{\mu(k) (\sum_{x = 1}^{\lfloor \frac{n}{k} \rfloor}{\lfloor \frac{\lfloor \frac{n}{k} \rfloor}{x} \rfloor}) (\sum_{y = 1}^{\lfloor \frac{m}{k} \rfloor}{\lfloor \frac{\lfloor \frac{m}{k} \rfloor}{y} \rfloor})}
\end{align*}
$$
设
$$
f(x) = \sum_{i = 1}^{x}{\lfloor \frac{x}{i} \rfloor}
$$
那么原式化为
$$
\sum_{k = 1}^{n}{\mu(k) f(\lfloor \frac{n}{k} \rfloor) f(\lfloor \frac{m}{k} \rfloor)}
$$
然后预处理 $f$ 的值,做二维数论分块即可。
时间复杂度 $O(n \sqrt{n} + q \sqrt{n})$。
### AC Code
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define uni unsigned int
#define ull unsigned long long
#define lll __int128
#define ld long double
#define pb push_back
#define mk make_pair
#define ppcnt __builtin_popcount
#define sort stable_sort
ll read()
{
ll k=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')k=k*10+c-'0',c=getchar();
return k*f;
}
void write(char c){putchar(c);}
int St[100],Top;
void write(int x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
void write(ll x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
int prime[50010],cnt=0;
bitset<50010> vis;
int mu[50010];
ll f[50010];
void init()
{
mu[1]=1;
for(int i=2;i<=50000;++i)
{
if(!vis[i])
{
mu[i]=-1;
prime[++cnt]=i;
}
for(int j=1;j<=cnt&&i*prime[j]<=50000;++j)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<=50000;++i)mu[i]+=mu[i-1];
for(int n=1;n<=50000;++n)
{
for(int l=1,r;l<=n;l=r+1)
{
r=n/(n/l);
f[n]+=1ll*(r-l+1)*(n/l);
}
}
}
void solve()
{
ll n=read(),m=read();
if(n>m)swap(n,m);
ll ans=0;
for(int l=1,r;l<=n;l=r+1)
{
r=min(n/(n/l),m/(m/l));
ans+=(mu[r]-mu[l-1])*f[n/l]*f[m/l];
}
write(ans),write('\n');
}
int main()
{
std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
init();
int T=read();
while(T--)solve();
return 0;
}
```
## [G Luogu P5150](https://www.luogu.com.cn/problem/P5150)
### 题目
给定 $n$,求有多少对正整数 $(i,j)$ 的 $\operatorname{lcm}$ 是 $n$。
$n \le 10^{16}$。
### Solution
考虑质因数分解,$n = \prod_{p}{p^{n_p}}$,那么 $\operatorname{lcm}(i,j) = n$ 的充要条件是 $\forall p , \max(i_p,j_p) = n_p$,那么对于所有质数是独立的,简单容斥得到对于某一个质数的选择方案数是 $2 n_p + 1$,根据乘法原理求积即可。
时间复杂度 $O(\sqrt{n})$。
### AC Code
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define uni unsigned int
#define ull unsigned long long
#define lll __int128
#define ld long double
#define pb push_back
#define mk make_pair
#define ppcnt __builtin_popcount
#define sort stable_sort
ll read()
{
ll k=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')k=k*10+c-'0',c=getchar();
return k*f;
}
void write(char c){putchar(c);}
int St[100],Top;
void write(int x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
void write(ll x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
int main()
{
std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
ll n=read(),ans=1;
for(ll i=2;i*i<=n;++i)
{
if(n%i!=0)continue;
int cnt=0;
while(n%i==0)n/=i,cnt++;
ans*=2*cnt+1;
}
if(n>1)ans*=3;
write(ans);
return 0;
}
```
## [H Luogu P6222](https://www.luogu.com.cn/problem/P6222)
[Luogu P6156](https://www.luogu.com.cn/problem/P6156)加强版
### 题目
$$
\sum_{i = 1}^{n}{\sum_{j = 1}^{n}{(i + j)^k \gcd(i,j) \mu^2(\gcd(i,j))}}
$$
原题 $T = 1 , n \le 5 \times 10^6 , k \le 10^{18}$。
加强版 $T \le 10^4 , n \le 10^7 , k < 2^{31}$,并取模 $2^{32}$。
### Solution
直接推式子
$$
\begin{align*}
&\sum_{i = 1}^{n}{\sum_{j = 1}^{n}{(i + j)^k \gcd(i,j) \mu^2(\gcd(i,j))}} \\
&= \sum_{d = 1}^{n}{d \mu^2(d) \sum_{i = 1}^{n}{\sum_{j = 1}^{n}{(i + j)^k [\gcd(i,j) = d]}}} \\
&= \sum_{d = 1}^{n}{d \mu^2(d) \sum_{i = 1}^{\lfloor \frac{n}{d} \rfloor}{\sum_{j = 1}^{\lfloor \frac{n}{d} \rfloor}{(id + jd)^k [\gcd(i,j) = 1]}}} \\
&= \sum_{d = 1}^{n}{d^{k + 1} \mu^2(d) \sum_{i = 1}^{\lfloor \frac{n}{d} \rfloor}{\sum_{j = 1}^{\lfloor \frac{n}{d} \rfloor}{(i + j)^k [\gcd(i,j) = 1]}}} \\
&= \sum_{d = 1}^{n}{d^{k + 1} \mu^2(d) \sum_{i = 1}^{\lfloor \frac{n}{d} \rfloor}{\sum_{j = 1}^{\lfloor \frac{n}{d} \rfloor}{(i + j)^k \sum_{x | i , x | j}{\mu(x)}}}} \\
&= \sum_{d = 1}^{n}{d^{k + 1} \mu^2(d) \sum_{x = 1}^{\lfloor \frac{n}{d} \rfloor}{\mu(x) x^k \sum_{i = 1}^{\lfloor \frac{n}{dx} \rfloor}{\sum_{j = 1}^{\lfloor \frac{n}{dx} \rfloor}{(i + j)^k}}}} \\
&= \sum_{t = 1}^{n}{(\sum_{d | t}{d^{k + 1} \mu^2(k) \mu(\frac{t}{d}) (\frac{t}{d})^k}) (\sum_{i = 1}^{\lfloor \frac{n}{t} \rfloor}{\sum_{j = 1}^{\lfloor \frac{n}{t} \rfloor}{(i + j)^k}})} \\
&= \sum_{t = 1}^{n}{t^k (\sum_{d | t}{d \mu^2(k) \mu(\frac{t}{d})}) (\sum_{i = 1}^{\lfloor \frac{n}{t} \rfloor}{\sum_{j = 1}^{\lfloor \frac{n}{t} \rfloor}{(i + j)^k}})}
\end{align*}
$$
此时,$t^k$ 和第二个和式已经可以筛了,那么考虑第三个和式怎么处理。
令 $f(x) = \sum_{i = 1}^{x}{\sum_{j = 1}^{x}{(i + j)^k}}$,我们拆开式子后能发现
$$
f(x) - f(x - 1) = 2 \sum_{i = x + 1}^{2x}{i^k} - (2x)^k
$$
和式通过预处理前缀和递推即可。
此时就可以数论分块做了。
时间复杂度 $O(n + T \sqrt{n})$。
### AC Code
普通版
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define uni unsigned int
#define ull unsigned long long
#define lll __int128
#define ld long double
#define pb push_back
#define mk make_pair
#define ppcnt __builtin_popcount
#define sort stable_sort
const ll mod=998244353;
ll read()
{
ll k=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')k=k*10+c-'0',c=getchar();
return k*f;
}
void write(char c){putchar(c);}
int St[100],Top;
void write(int x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
void write(ll x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
ll fpow(ll x,ll y)
{
ll res=1;
while(y)
{
if(y&1)res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
int prime[5000010],cnt=0;
bitset<10000010> vis;
ll idk[10000010],h[10000010],s[5000010];
void init(ll n,ll k)
{
idk[1]=h[1]=1;
for(ll i=2;i<=n;++i)
{
if(!vis[i])
{
prime[++cnt]=i;
h[i]=i-1;
idk[i]=fpow(i,k);
}
for(int j=1;j<=cnt&&i*prime[j]<=n;++j)
{
vis[i*prime[j]]=1;
idk[i*prime[j]]=idk[i]*idk[prime[j]]%mod;
if(i%prime[j]==0)
{
if((i/prime[j])%prime[j]!=0)h[i*prime[j]]=(mod-prime[j])*h[i/prime[j]]%mod;
else h[i*prime[j]]=0;
break;
}
h[i*prime[j]]=h[i]*h[prime[j]]%mod;
}
}
for(int i=1;i<=n;++i)(h[i]*=idk[i])%=mod;
for(int i=1;i<=n;++i)(h[i]+=h[i-1])%=mod;
for(int i=1;i<=n;++i)(idk[i]+=idk[i-1])%=mod;
for(int i=1;i<=n;++i)(idk[i]+=idk[i-1])%=mod;
for(int i=1;i<=(n>>1);++i)s[i]=(idk[i*2]-2*idk[i]%mod+mod)%mod;
}
int main()
{
std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
ll n=read(),k=read();
init(n<<1,k);
ll ans=0;
for(ll l=1,r;l<=n;l=r+1)
{
r=min(n,n/(n/l));
(ans+=(h[r]-h[l-1])%mod*s[n/l]%mod)%=mod;
}
write((ans+mod)%mod);
return 0;
}
```
加强版
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define uni unsigned int
#define ull unsigned long long
#define lll __int128
#define ld long double
#define pb push_back
#define mk make_pair
#define ppcnt __builtin_popcount
#define sort stable_sort
ll read()
{
ll k=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')k=k*10+c-'0',c=getchar();
return k*f;
}
void write(char c){putchar(c);}
int St[100],Top;
void write(int x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
void write(uni x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
uni fpow(uni x,ll y)
{
uni res=1;
while(y)
{
if(y&1)res=res*x;
x=x*x;
y>>=1;
}
return res;
}
int prime[10000010],cnt=0;
bitset<20000010> vis;
uni idk[20000010],h[20000010],s[10000010];
void init(uni n,ll k)
{
idk[1]=h[1]=1;
for(uni i=2;i<=n;++i)
{
if(!vis[i])
{
prime[++cnt]=i;
h[i]=i-1;
idk[i]=fpow(i,k);
}
for(uni j=1;j<=cnt&&i*prime[j]<=n;++j)
{
vis[i*prime[j]]=1;
idk[i*prime[j]]=idk[i]*idk[prime[j]];
if(i%prime[j]==0)
{
if((i/prime[j])%prime[j]!=0)h[i*prime[j]]=-prime[j]*h[i/prime[j]];
else h[i*prime[j]]=0;
break;
}
h[i*prime[j]]=h[i]*h[prime[j]];
}
}
for(int i=1;i<=n;++i)h[i]*=idk[i];
for(int i=1;i<=n;++i)h[i]+=h[i-1];
for(int i=1;i<=n;++i)idk[i]+=idk[i-1];
for(int i=1;i<=n;++i)idk[i]+=idk[i-1];
for(int i=1;i<=(n>>1);++i)s[i]=idk[i*2]-2*idk[i];
}
ll k;
void solve()
{
uni n=read();
uni ans=0;
for(uni l=1,r;l<=n;l=r+1)
{
r=min(n,n/(n/l));
ans+=(h[r]-h[l-1])*s[n/l];
}
write(ans),write('\n');
}
int main()
{
std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int T=read();
uni N=read();
k=read();
init(N<<1,k);
while(T--)solve();
return 0;
}
```
## [I AT_agc038_c](https://atcoder.jp/contests/agc038/tasks/agc038_c)
### 题目
给定 $n$ 个正整数 $a_1 \cdots a_n$,求
$$
\sum_{i = 1}^{n - 1}{\sum_{j = i + 1}^{n}{\operatorname{lcm}(a_i,a_j)}}
$$
$n \le 2 \times 10^5 , a_i \le 10^6$。
### Solution
首先发现式子不齐,于是补齐在考虑容掉。
然后答案变为
$$
Ans = \frac{\sum_{i = 1}^{n}{\sum_{j = 1}^{n}{\operatorname{lcm}(a_i,a_j)}} - \sum_{i = 1}^{n}{a_i}}{2}
$$
然后我们只需要处理第一个和式。
发现值域很小,于是开桶,用 $c_i$ 表示有多少数为 $i$。
然后大力推式子,用 $V$ 表示值域范围
$$
\begin{align*}
&\sum_{i = 1}^{n}{\sum_{j = 1}^{n}{\operatorname{lcm}(a_i,a_j)}} \\
&= \sum_{i = 1}^{V}{\sum_{j = 1}^{V}{\operatorname{lcm}(i,j) c_i c_j}} \\
&= \sum_{i = 1}^{V}{\sum_{j = 1}^{V}{i j c_i c_j \gcd(i,j)^{-1}}} \\
&= \sum_{d = 1}^{V}{d^{-1} \sum_{i = 1}^{V}{\sum_{j = 1}^{V}{i j c_i c_j [\gcd(i,j) = d]}}} \\
&= \sum_{d = 1}^{V}{d^{-1} \sum_{i = 1}^{\lfloor \frac{V}{d} \rfloor}{\sum_{j = 1}^{\lfloor \frac{V}{d} \rfloor}{id jd c_{id} c_{jd} [\gcd(i,j) = 1]}}} \\
&= \sum_{d = 1}^{V}{d \sum_{i = 1}^{\lfloor \frac{V}{d} \rfloor}{\sum_{j = 1}^{\lfloor \frac{V}{d} \rfloor}{i j c_{id} c_{id} \sum_{k | i , k | j}{\mu(k)}}}} \\
&= \sum_{d = 1}^{V}{d \sum_{k = 1}^{\lfloor \frac{V}{d} \rfloor}{ \mu(k) \sum_{i = 1}^{\lfloor \frac{V}{dk} \rfloor}{\sum_{j = 1}^{\lfloor \frac{V}{dk} \rfloor}{i j c_{idk} c_{jdk}}}}} \\
&= \sum_{d = 1}^{V}{d \sum_{k = 1}^{\lfloor \frac{V}{d} \rfloor}{\mu(k) (\sum_{i = 1}^{\lfloor \frac{V}{dk} \rfloor}{i c_{idk}})^2}} \\
&= \sum_{t = 1}^{V}{(\sum_{d | t}{d \mu(\frac{t}{d})}) (\sum_{i = 1}^{\lfloor \frac{V}{t} \rfloor}{i c_{it}})^2}
\end{align*}
$$
现在,我们发现第一个和式是一个积性函数,直接筛即可。第二个是一个调和级数复杂度预处理的东西,最后枚举 $t$ 统计答案即可。
时间复杂度 $O(V \ln V)$。
### AC Code
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define uni unsigned int
#define ull unsigned long long
#define lll __int128
#define ld long double
#define pb push_back
#define mk make_pair
#define ppcnt __builtin_popcount
#define sort stable_sort
const ll mod=998244353,inv2=499122177;
ll read()
{
ll k=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')k=k*10+c-'0',c=getchar();
return k*f;
}
void write(char c){putchar(c);}
int St[100],Top;
void write(int x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
void write(ll x)
{
if(x==0){putchar('0');return;}
if(x<0)putchar('-'),x=-x;
Top=0;while(x)St[++Top]=x%10,x/=10;
while(Top)putchar((char)('0'+St[Top--]));
}
int n,ma=0;
ll c[1000010];
ll sum=0;
int cnt=0,prime[1000010];
bitset<1000010> vis;
ll mu[1000010],f[1000010],g[1000010];
void init()
{
mu[1]=1;
for(int i=2;i<=ma;++i)
{
if(!vis[i])
{
prime[++cnt]=i;
mu[i]=-1;
}
for(int j=1;j<=cnt&&i*prime[j]<=ma;++j)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<=ma;++i)mu[i]*=i;
for(int i=1;i<=ma;++i)for(int j=i;j<=ma;j+=i)(f[j]+=mu[i])%=mod;
for(int t=1;t<=ma;++t)for(int i=1;i<=ma/t;++i)(g[t]+=i*c[i*t]%mod)%=mod;
for(int i=1;i<=ma;++i)g[i]=g[i]*g[i]%mod;
}
int main()
{
std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
memset(c,0,sizeof(c));
n=read();
for(int i=1;i<=n;++i)
{
int a=read();
c[a]++;
ma=max(ma,a);
sum+=a;
}
init();
ll ans=0;
for(int i=1;i<=ma;++i)(ans+=i*f[i]%mod*g[i]%mod)%=mod;
(ans-=sum)%=mod;
ans=ans*inv2%mod;
write((ans+mod)%mod);
return 0;
}
```