洛谷 P2398 GCD SUM || uva11417,uva11426,uva11424,洛谷P1390,洛谷P2257,洛谷P2568
https://www.luogu.org/problemnew/show/P2398
$原式=\sum_{k=1}^n(k\sum_{i=1}^n\sum_{j=1}^n[(i,j)=k])$
方法1:
发现暴力枚举k,就变成这道模板题
复杂度O(nlogn)
1 #pragma GCC optimize("Ofast") 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 #include<vector> 6 using namespace std; 7 #define fi first 8 #define se second 9 #define mp make_pair 10 #define pb push_back 11 typedef long long ll; 12 typedef unsigned long long ull; 13 typedef pair<int,int> pii; 14 #define N 2000000 15 ll prime[N+100],len,mu[N+100]; 16 bool nprime[N+100]; 17 ll n,ans,a2; 18 ll F(ll x) {return (n/x)*(n/x);} 19 int main() 20 { 21 ll i,j,k; 22 mu[1]=1; 23 for(i=2;i<=N;i++) 24 { 25 if(!nprime[i]) prime[++len]=i,mu[i]=-1; 26 for(j=1;j<=len&&i*prime[j]<=N;j++) 27 { 28 nprime[i*prime[j]]=1; 29 if(i%prime[j]==0) {mu[i*prime[j]]=0;break;} 30 else mu[i*prime[j]]=-mu[i]; 31 } 32 } 33 scanf("%lld",&n); 34 for(k=1;k<=n;k++) 35 { 36 a2=0; 37 for(i=1;i<=n/k;i++) 38 a2+=mu[i]*F(i*k); 39 ans+=a2*k; 40 } 41 printf("%lld",ans); 42 return 0; 43 }
方法2:
https://www.luogu.org/blog/Cinema/solution-p1390
(这是一道类似的题的题解)
复杂度好像是O(n)
具体地说,是嵌套整除分块,第一重枚举n/d,m/d,第二重枚举(n/d)/x,(m/d)/x
方法3:
$\sum_{i=1}^n\sum_{j=1}^n[(i,j)=k]=\sum_{i=1}^{{\lfloor}n/k{\rfloor}}\sum_{j=1}^{{\lfloor}n/k{\rfloor}}[(i,j)=1]$
$=2*(\sum_{i=1}^{{\lfloor}n/k{\rfloor}}\sum_{j=1}^{i}[(i,j)=1])-1=2*(\sum_{i=1}^{{\lfloor}n/k{\rfloor}}\varphi(i))-1$
这个欧拉函数的前缀和可以先预处理出来,然后枚举k
复杂度O(n)
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<vector> 5 using namespace std; 6 #define fi first 7 #define se second 8 #define mp make_pair 9 #define pb push_back 10 typedef long long ll; 11 typedef unsigned long long ull; 12 typedef pair<int,int> pii; 13 #define N 2000000 14 ll prime[N+100],len,phi[N+100]; 15 ll d[N+100]; 16 bool nprime[N+100]; 17 ll n,ans; 18 ll F(ll x) {return (n/x)*(n/x);} 19 int main() 20 { 21 ll i,j,k; 22 phi[1]=1; 23 for(i=2;i<=N;i++) 24 { 25 if(!nprime[i]) prime[++len]=i,phi[i]=i-1; 26 for(j=1;j<=len&&i*prime[j]<=N;j++) 27 { 28 nprime[i*prime[j]]=1; 29 if(i%prime[j]==0) {phi[i*prime[j]]=phi[i]*prime[j];break;} 30 else phi[i*prime[j]]=phi[i]*(prime[j]-1); 31 } 32 } 33 for(i=1;i<=N;i++) d[i]+=d[i-1]+phi[i]; 34 scanf("%lld",&n); 35 for(k=1;k<=n;k++) ans+=k*(2*d[n/k]-1); 36 printf("%lld",ans); 37 return 0; 38 }
方法4:
注意到方法3第35行的可以用整除分块优化,甚至可以O(n)预处理后每次O(sqrt(n))回答
类似的题(题面并不完全一样)
https://www.luogu.org/problemnew/show/UVA11417
https://www.luogu.org/problemnew/show/UVA11426
https://www.luogu.org/problemnew/show/P1390
https://www.luogu.org/problemnew/show/UVA11424
这题做法稍微有点不一样,因为数据组数多,不能直接每次O(n)回答
可以用方法4水过去
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<vector> 5 using namespace std; 6 #define fi first 7 #define se second 8 #define mp make_pair 9 #define pb push_back 10 typedef long long ll; 11 typedef unsigned long long ull; 12 typedef pair<int,int> pii; 13 #define N 2000000 14 ll prime[N+100],len,phi[N+100]; 15 ll d[N+100]; 16 bool nprime[N+100]; 17 ll n,ans; 18 ll F(ll x) {return (n/x)*(n/x);} 19 int main() 20 { 21 ll i,j,k; 22 phi[1]=1; 23 for(i=2;i<=N;i++) 24 { 25 if(!nprime[i]) prime[++len]=i,phi[i]=i-1; 26 for(j=1;j<=len&&i*prime[j]<=N;j++) 27 { 28 nprime[i*prime[j]]=1; 29 if(i%prime[j]==0) {phi[i*prime[j]]=phi[i]*prime[j];break;} 30 else phi[i*prime[j]]=phi[i]*(prime[j]-1); 31 } 32 } 33 for(i=1;i<=N;i++) d[i]+=d[i-1]+phi[i]; 34 while(1){ 35 scanf("%lld",&n); 36 if(n==0) break; 37 ans=0; 38 for(i=1;i<=n;i=j+1) 39 { 40 j=min(n,n/(n/i)); 41 ans+=(i+j)*(j-i+1)/2*(2*d[n/i]-1); 42 } 43 printf("%lld\n",(ans-(n+1)*n/2)/2);} 44 return 0; 45 }
或者:
设sum[x]=$\sum_{i=1}^{x-1}(i,x)$
那么询问为n时,答案就是$\sum_{i=1}^n{sum[i]}$
而sum[x]=$\sum_{k=1}^{x-1}(k*\sum_{i=1}^{x-1}[(i,x)=k])$
显然只有当k是x的因子且k不等于x时才能有贡献
枚举k,$\sum_{i=1}^{x-1}[(i,x)=k]=\sum_{i=1}^{\frac{x}{k}-1}[(i,\frac{x}{k})=1]$
$=(\sum_{i=1}^{\frac{x}{k}}[(i,\frac{x}{k})=1])=\phi(\frac{x}{k})$
可以看出来sum[x]=$(\sum_{d|x且d{\neq}x}(d*\phi(\frac{x}{d})))$
实际实现时可以把枚举因子变为枚举倍数
复杂度所有数据加起来O(nlogn+q)
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<vector> 5 using namespace std; 6 #define fi first 7 #define se second 8 #define mp make_pair 9 #define pb push_back 10 typedef long long ll; 11 typedef unsigned long long ull; 12 typedef pair<int,int> pii; 13 #define N 200000 14 ll prime[N+100],len,phi[N+100]; 15 ll sum[N+100]; 16 bool nprime[N+100]; 17 ll n,ans[N+100]; 18 int main() 19 { 20 ll i,j,k; 21 phi[1]=1; 22 for(i=2;i<=N;i++) 23 { 24 if(!nprime[i]) prime[++len]=i,phi[i]=i-1; 25 for(j=1;j<=len&&i*prime[j]<=N;j++) 26 { 27 nprime[i*prime[j]]=1; 28 if(i%prime[j]==0) {phi[i*prime[j]]=phi[i]*prime[j];break;} 29 else phi[i*prime[j]]=phi[i]*(prime[j]-1); 30 } 31 } 32 for(i=1;i<=N;i++) 33 for(j=2*i;j<=N;j+=i) 34 sum[j]+=i*phi[j/i]; 35 for(i=1;i<=N;i++) ans[i]=ans[i-1]+sum[i]; 36 while(1){ 37 scanf("%lld",&n); 38 if(n==0) break; 39 printf("%lld\n",ans[n]); 40 } 41 return 0; 42 }
https://www.luogu.org/problemnew/show/P2257
事实上此题做法与以上几题是一样的,如果数据范围一样。。
然而此题有多组数据又是1e7,需要更好的做法
不妨设n<=m
根据方法2,得到答案是$\sum_{1<=k<=n且k为质数}\sum_{i=1}^n\mu(i){\lfloor}\frac{n}{ik}{\rfloor}{\lfloor}\frac{m}{ik}{\rfloor}$
好像不能化简了?然而A不了题?
强行解释一波推算方法:注意到最终的可能求法是整除分块,要枚举ik的积,那么试着把它提出来
第二种解释方法:为什么要这么写式子?学莫比乌斯反演时学来的式子枚举的就是d啊
看了题解,发现可以令d=ik
答案就等于$\sum_{1<=k<=n且k为质数}\sum_{k|d}\mu(\frac{d}{k}){\lfloor}\frac{n}{d}{\rfloor}{\lfloor}\frac{m}{d}{\rfloor}$
$=\sum_{d=1}^n\sum_{k|d且k为质数}\mu(\frac{d}{k}){\lfloor}\frac{n}{d}{\rfloor}{\lfloor}\frac{m}{d}{\rfloor}$
$\sum_{k|d且k为质数}\mu(\frac{d}{k})$可以预处理了,根据那个质数个数(近似)公式,预处理复杂度大概接近O(n)了。。
可以A了。。。
1 //#pragma GCC optimize("Ofast") 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 #include<vector> 6 using namespace std; 7 #define fi first 8 #define se second 9 #define mp make_pair 10 #define pb push_back 11 typedef long long ll; 12 typedef unsigned long long ull; 13 typedef pair<int,int> pii; 14 #define N 10000000 15 int prime[N+100],len,mu[N+100],dd[N+100]; 16 bool nprime[N+100]; 17 int n,m;ll ans; 18 int main() 19 { 20 int i,j,k,T,TT; 21 mu[1]=1; 22 for(i=2;i<=N;i++) 23 { 24 if(!nprime[i]) prime[++len]=i,mu[i]=-1; 25 for(j=1;j<=len&&i*prime[j]<=N;j++) 26 { 27 nprime[i*prime[j]]=1; 28 if(i%prime[j]==0) {mu[i*prime[j]]=0;break;} 29 else mu[i*prime[j]]=-mu[i]; 30 } 31 } 32 for(i=1;i<=len;i++) 33 for(j=1,k=prime[i];j<=N/prime[i];j++,k+=prime[i]) 34 dd[k]+=mu[j]; 35 for(i=1;i<=N;i++) dd[i]+=dd[i-1]; 36 scanf("%d",&T); 37 for(TT=1;TT<=T;TT++) 38 { 39 scanf("%d%d",&n,&m); 40 if(n>m) swap(n,m); 41 ans=0; 42 for(i=1;i<=n;i=j+1) 43 { 44 j=min(n,min(n/(n/i),m/(m/i))); 45 ans+=ll(dd[j]-dd[i-1])*(n/i)*(m/i); 46 } 47 printf("%lld\n",ans); 48 } 49 return 0; 50 }
双倍经验https://www.luogu.org/problemnew/show/P2568
好吧以上这题时限有点紧。。不过单次询问O(n)也可以用方法3或者方法2