传送门
BZOJ2154
题意:
求 ∑ni=1∑mj=1lcm(i,j) ∑ i = 1 n ∑ j = 1 m l c m ( i , j )
Solution:
看到此题首先想到这步:
然后开始拼命化简,未果
听完dalao的提示:莫比乌斯反演题一定要想办法搞掉分母,想办法枚举约数
想到了下一步:
=∑min(n,m)d=1d∑⌊nd⌋i=1∑⌊md⌋j=1ij[gcd(i,j)==1] = ∑ d = 1 m i n ( n , m ) d ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 ⌊ m d ⌋ i j [ g c d ( i , j ) == 1 ]
就这样把式子拆成了两部分,现在我们要做的就是化简后面的部分:
=∑min(n,m)d=1d∑min(⌊nd⌋,⌊md⌋)g=1μ(g)∑⌊ndg⌋i=1∑⌊mdg⌋j=1ig∗jg = ∑ d = 1 m i n ( n , m ) d ∑ g = 1 m i n ( ⌊ n d ⌋ , ⌊ m d ⌋ ) μ ( g ) ∑ i = 1 ⌊ n d g ⌋ ∑ j = 1 ⌊ m d g ⌋ i g ∗ j g
=∑min(n,m)d=1d∑min(⌊nd⌋,⌊md⌋)g=1μ(g)∗g2∗⌊ndg⌋(⌊ndg⌋+1)2∗⌊mdg⌋(⌊mdg⌋+1)2 = ∑ d = 1 m i n ( n , m ) d ∑ g = 1 m i n ( ⌊ n d ⌋ , ⌊ m d ⌋ ) μ ( g ) ∗ g 2 ∗ ⌊ n d g ⌋ ( ⌊ n d g ⌋ + 1 ) 2 ∗ ⌊ m d g ⌋ ( ⌊ m d g ⌋ + 1 ) 2
运用传统的分块优化就可以求出答案了
代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int N=10000010;
const int mod=20101009;
bool vis[N];
int sum[N],n,m;
int prim[1000010],cnt;
int ans;
int mu[N];
void get_prim(int x)
{
mu[1]=1;sum[1]=1;
vis[1]=1;
for (int i=2;i<=x;i++)
{
if (!vis[i]) prim[++cnt]=i,mu[i]=-1;
for (int j=1;j<=cnt&&i*prim[j]<=x;j++)
{
vis[i*prim[j]]=1;
if (i%prim[j]==0) {mu[i*prim[j]]=0;break;}
mu[i*prim[j]]=-mu[i];
}
sum[i]=(1ll*sum[i-1]+1ll*i*i*mu[i])%mod;
}
}
int SUM(int x,int y)
{
return (1ll*(1ll*x*(x+1)/2)%mod*((1ll*y*(y+1)/2)%mod))%mod;
}
int calc(int x,int y)
{
int last=0,t=min(x,y);
int cnt=0;
for (int i=1;i<=t;i=last+1)
{
last=min(x/(x/i),y/(y/i)); cnt=(cnt+(sum[last]-sum[i-1])%mod*1ll*SUM(x/i,y/i)%mod+mod)%mod; } return cnt; } int main() { scanf("%d%d",&n,&m); get_prim(min(n,m)); int last=0,t=min(n,m); for (int i=1;i<=t;i=last+1) { last=min(n/(n/i),m/(m/i));
ans=(1ll*ans+(1ll*(last+i)*(last-i+1)/2)%mod*1ll*calc(n/i,m/i))%mod; } printf("%d",ans); }
加强版:BZOJ2693
加入了多组数据,导致了上面的做***T
所以我们需要优化
再次变形上面的式子:设 D=dg D = d g
=∑min(n,m)D=1⌊nD⌋(⌊nD⌋+1)2∗⌊mD⌋(⌊mD⌋+1)2∑g|DDg∗μ(g)∗g2 = ∑ D = 1 m i n ( n , m ) ⌊ n D ⌋ ( ⌊ n D ⌋ + 1 ) 2 ∗ ⌊ m D ⌋ ( ⌊ m D ⌋ + 1 ) 2 ∑ g | D D g ∗ μ ( g ) ∗ g 2
我们观察后面的那一坨东西:
μ(g)∗g2 μ ( g ) ∗ g 2 是积性函数,设 f(g)=μ(g)∗g2 f ( g ) = μ ( g ) ∗ g 2 ,那么 ∑g|DDg∗μ(g) ∑ g | D D g ∗ μ ( g ) 相当于是 id∗f i d ∗ f ,两个积性函数的卷积还是积性函数,所以后面的那一坨是个积性函数,这样我们就可以再线性筛的时候顺便求一下就可以了,
这样复杂度就少了 n−−√ n
代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int N=10000010;
const int mod=100000009;
bool vis[N];
int sum[N],n,m,T;
int prim[1000010],cnt;
int ans;
int mu[N];
void get_prim(int x)
{
mu[1]=1;
vis[1]=1;
for (int i=2;i<=x;i++)
{
if (!vis[i]) prim[++cnt]=i,mu[i]=(i-1ll*i*i)%mod;
for (int j=1;j<=cnt&&i*prim[j]<=x;j++)
{
vis[i*prim[j]]=1;
if (i%prim[j]==0) {mu[i*prim[j]]=1ll*prim[j]*mu[i]%mod;break;}
mu[i*prim[j]]=(1ll*mu[prim[j]]*mu[i])%mod;
}
}
for (int i=1;i<=x;i++)
sum[i]=(sum[i-1]+mu[i])%mod;
}
int SUM(int x,int y)
{
return (1ll*(1ll*x*(x+1)/2)%mod*((1ll*y*(y+1)/2)%mod))%mod;
}
int calc(int x,int y)
{
int last=0,t=min(x,y);
int cnt=0;
for (int i=1;i<=t;i=last+1)
{
last=min(x/(x/i),y/(y/i)); cnt=(cnt+(sum[last]-sum[i-1])%mod*1ll*SUM(x/i,y/i)%mod+mod)%mod;
}
return cnt;
}
int main()
{
get_prim(10000000);
scanf("%d",&T);
while (T--)
{
scanf("%d%d",&n,&m);
ans=0;
int last=0,t=min(n,m);
ans=calc(n,m);
printf("%d\n",ans);
}
}