【题解】 [EZEC-4]求和

对于百分之十的数据:随便过。

下面推式子:

i=1nj=1ngcd(i,j)i+j

=d=1ni=1nj=1ndi+j[gcd(i,j)=d]

=d=1ni=1ndj=1nddd(i+j)[gcd(i,j)=1]

=d=1ni=1ndj=1nddd(i+j)k|gcd(i,j)μ(k)

=d=1nk=1ndμ(k)i=1nkdj=1nkddkd(i+j)

T=kd:

=T=1nk|Tμ(k)i=1nTj=1nT[(Tk)T]i+j

现在的问题在于i=1nTj=1nT[(Tk)T]i+j.

  • 线性递推

以下是@SOSCHINA大佬的思路:

g(n)=i=1nj=1nks.

枚举s=i+j.

则有:

g(n)=s=2n+1(s1)ks+s=n+22n(2ns+1)ks

g(n+1)=s=2n+1(s1)ks+s=n+22n+2(sn+3s)ks

g(n+1)g(n)=s=n+22n2ks+2k2n+1+k2n+2

第三行就是两行相减。

对第一行的解释:[2,n+1]这里的数,每个数作为i+j都出现了x1次。因为i可以取遍[1,x1].后面的那一些,[n+2,2n]会发现i最大只能到n,不能再取遍x1个值了。此时能取到的应该是2ns+1种。

对于g(n+1):这里是把第一个式子的最后一个值移动到了后面那个式子,方便做差。

这时我们可以在小模数的情况下做到O(nmod)的预处理。

  • 化简形式

x=(Tk)T.

则原式为i=1nTj=1nTxi+j.

像不像一个多项式。

它就等于(x+x2+...xnT)2.

于是我们可以等比数列求和解出。

剩下的,可以做到O(nlognlogmod)处理出整个式子。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1500001;
int mod,TT;
bitset<MAXN+1>vis;
int p[MAXN+1],mu[MAXN+1],T[MAXN+1],cnt,n,Ans;
inline int Mod(long long x){
	if(x<0)return x+mod;
    if(x>=mod)return x%mod;
    return x;
}
inline int add(int x,int y) {return Mod(1ll*x+1ll*y+1ll*mod);}
inline int mul(int x,int y) {return Mod(1ll*x*y);}
inline int qpow(int a,int b) {
	if(!b)return 1;
	if(a<=1||b==1)return a;
	a %= mod; 
	int res=1;
	while(b) {
		if(b&1)res=mul(res,a);
		a=mul(a,a);
		b>>=1;
	}
	return res;
}
inline int calc(int x,int y){
	if(y==1)return x;
	if(x==1)return y;
	int ans=x;
	int inv=qpow((1-x+mod)%mod,mod-2);
	int fm=(1-qpow(x,y)+mod)%mod;
	ans=mul(ans,mul(fm,inv));
	return ans;
}
inline int Calc(int x,int y){int ans=calc(x,y);return mul(ans,ans);} 
int main() {
	scanf("%d",&TT);
	mu[1]=1;
	int N=MAXN;
	for(register int i=2; i<=N; ++i) {
		if(!vis[i])p[++cnt]=i,mu[i]=-1;
		for(register int j=1; j<=cnt&&i*p[j]<=N; ++j) {
			vis[i*p[j]]=1;
			if(i%p[j]==0)break;
			mu[i*p[j]]=-mu[i];
		}
	}
	while(TT--) {
		scanf("%d%d",&n,&mod);
		N=n;Ans=0;
		for(register int i=1; i<=N; ++i) {
			for(register int j=i,k,x; j<=N; j+=i) {
				k=i;if(!mu[k])continue;
				x=qpow(j/k,j);
				T[j]=add(T[j],mul(mu[k],Calc(x,n/j)));
			}
		}
		for(register int i=1; i<=n; ++i)Ans=add(Ans,T[i]),T[i]=0;
		printf("%d\n",Ans);
	}

	return 0;
}

由于这里是5105的数据,所以略微卡常,但笔者通过非常不精湛的卡常技术跑到了3s以内,所以这里的时间限制我开了3.2s.

对等比数列进行精细处理,可以做到O(nlog2n)的复杂度。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=1500000;
int mod,TT;
bitset<MAXN<<1>vis;
int p[MAXN<<1],mu[MAXN<<1],T[MAXN<<1],cnt,n,Ans;
inline int Mod(long long x){
    if(x>=mod)return x%mod;
    return x;
}
inline int add(int x,int y) {
	return Mod(x+y+mod);
}
inline int mul(int x,int y) {
	return Mod(1ll*x*y);
}
inline int qpow(int a,int b) {
	if(!b)return 1;
	if(a<=1||b==1)return a;
	a %= mod; 
	int res=1;
	while(b) {
		if(b&1)res=mul(res,a);
		a=mul(a,a);
		b>>=1;
	}
	return res;
}
inline int calc(int x,int y){
	if(y==1)return x;
		int res=calc(x,y/2);
	res=add(res,mul(res,qpow(x,y/2)));
	if(y&1)res=add(res,mul(x,qpow(x,y-1)));
	return res;
}
inline int Calc(int x,int y){int ans=calc(x,y);return mul(ans,ans);} 
signed main() {
	scanf("%lld",&TT);
	mu[1]=1;
	int N=MAXN;
	for(int i=2; i<=N; ++i) {
		if(!vis[i])p[++cnt]=i,mu[i]=-1;
		for(int j=1; j<=cnt&&i*p[j]<=N; ++j) {
			vis[i*p[j]]=1;
			if(i%p[j]==0)break;
			mu[i*p[j]]=-mu[i];
		}
	}
	while(TT--) {
		scanf("%lld%lld",&n,&mod);
		N=n;
		Ans=0;
		for(int i=1; i<=N; ++i) {
			for(int j=i; j<=N; j+=i) {
				int k=i;
				int x=qpow(j/k,j);
				if(!mu[k])continue;
				T[j]=add(T[j],mul(mu[k],Calc(x,n/j)));
			}
		}
		for(int i=1; i<=n; ++i)Ans=add(Ans,T[i]),T[i]=0;
		cout<<Ans<<endl;
	}

	return 0;
}

由于常数等原因,这分代码可以拿到50分的好成绩。但我们可以通过另一种做法将常数/复杂度降低。

另一种做法

观察:

d=1nk=1ndμ(k)i=1nkdj=1nkddkd(i+j)

=d=1nk=1ndμ(k)(dkd+d2kd+...+dnkdkd=n)2.

这里同样观察式子发现可以直接算。前一部分是O(nlnn)n倍调和级数的复杂度,后面带上一个O(logn)精细处理的等比数列求求和复杂度。

(代码中的优化即使不加也是可以过的)

#define __AVX__ 1
#define __AVX2__ 1
#define __SSE__ 1
#define __SSE2__ 1
#define __SSE2_MATH__ 1
#define __SSE3__ 1
#define __SSE4_1__ 1
#define __SSE4_2__ 1
#define __SSE_MATH__ 1
#define __SSSE3__ 1
#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")
#include <immintrin.h>
#include <emmintrin.h>
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <bitset>
using namespace std;
const int MAXN=1.5e6+10;
int mod,T;
bitset<MAXN+1>vis;
int p[MAXN+1],cnt,mu[MAXN+1],N;
inline int Mod(long long a, int pp){
    return a>=pp ? a%pp : a>=0 ? a : a+pp;
}
inline int add(int x,int y){return Mod( (1ll+x+y+mod-1ll),mod);}
inline int mul(int x,int y){return Mod(1ll*x*y,mod);}
void pretreatment(){
	mu[1]=1;
	for(int i=2;i<=MAXN;++i){
		if(!vis[i])p[++cnt]=i,mu[i]=-1;
		for(int j=1;j<=cnt&&i*p[j]<=MAXN;++j){
			vis[i*p[j]]=1;
			if(Mod(i,p[j])==0)break;
			mu[i*p[j]]=-mu[i];
		}
	}
}
inline int qpow(int a,int b){
	if(!b)return 1;
	if(a<=1||b==1)return a;
	int res=1;
	while(b){
		if(b&1)res=mul(res,a);
		a=mul(a,a);b>>=1;
	}
	return res;
}
inline int calc(int x,int y){
	if(y==1)return x;
	int res=calc(x,y>>1);
	res=add(res,mul(res,qpow(x,y>>1)));
	if(y&1)res=add(res,mul(x,qpow(x,y-1)));
	return res;
}

inline int Calc(int x,int y){int ans=calc(x,y);return mul(ans,ans);} 
int ssolve(int n,int d){
	int res=0;
	for(register int l=1;l<=n;++l){
		if(!mu[l])continue;
		res=add(res,mul(mu[l],Calc(qpow(d,l),n/l)));
	}
	return res;
}
int solve(int n){
	int ans=0;
	for(register int l=1;l<=n;l++){
		ans=add(ans,ssolve(n/l,qpow(l,l)));
	}
	return ans;
}
signed main(){
	scanf("%lld",&T);
	pretreatment();
	for(;T;T--){
		scanf("%lld%lld",&N,&mod);
		printf("%lld\n",solve(N));
	}
	return 0;
}

可以用整除分块减少循环中乘法的使用,对代码速度可能有一定的提升。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1.5e6+10;
int mod,T;
bitset<MAXN+1>vis;
int p[MAXN+1],cnt,mu[MAXN+1],N;
inline int Mod(long long a, int pp){return a>=pp ? a%pp : a>=0 ? a : a+pp;}
inline int add(int x,int y){return Mod( (1ll+x+y+mod-1ll),mod);}
inline int mul(int x,int y){return Mod(1ll*x*y,mod);}
inline int qpow(int a,int b){
	if(!b)return 1;
	if(a<=1||b==1)return a;
	a=Mod(a,mod);
	int res=1;
	while(b){
		if(b&1)res=mul(res,a);
		a=mul(a,a);b>>=1;
	}
	return res;
}
inline int calc(int x,int y){
	if(y==1)return x;
	int res=calc(x,y>>1);
	res=add(res,mul(res,qpow(x,y>>1)));
	if(y&1)res=add(res,mul(x,qpow(x,y-1)));
	return res;
}
inline int Calc(int x,int y){int ans=calc(x,y);return mul(ans,ans);} 
int ssolve(int n,int d){
	int res=0;
	for(register int l=1,r;l<=n;l=r+1){
		r=(n/(n/l));
		int D=n/l;
		for(int i=l;i<=r;++i){
			if(!mu[i])continue;
			res=add(res,mul(mu[i],Calc(qpow(d,i),D)));
		}
	}
	return res;
}
int solve(int n){
	int ans=0;
	for(register int l=1,r;l<=n;l=r+1){
		r=(n/(n/l));
		int D=n/l;
		for(int i=l;i<=r;++i)ans=add(ans,ssolve(D,qpow(i,i)));
	}
	return ans;
}
int main(){
	scanf("%d",&T);
	mu[1]=1;
	for(register int i=2;i<=MAXN;++i){
		if(!vis[i])p[++cnt]=i,mu[i]=-1;
		for(register int j=1;j<=cnt&&i*p[j]<=MAXN;++j){
			vis[i*p[j]]=1;
			if(Mod(i,p[j])==0)break;
			mu[i*p[j]]=-mu[i];
		}
	}
	for(;T;T--){
		scanf("%d%d",&N,&mod);
		printf("%d\n",solve(N));
	}
	return 0;
}

经由大佬 @C3H5ClO 大佬证明,上面这份代码实际上是O(nlogn)的。

这里借用一下 @C3H5ClO 大佬的证明:

f(n)=d=1ne=1ndμ(e)(i=1ndedde)2

T(n)=O(d=1ne=1ndlognde)

T(n)=O(i=1n0nilognixdx)

lognxdx=(logn+1)xxlogx+C

0nlognxdx=n

T(n)=O(i=1nni)=O(nlnn)

(蒟蒻不会微积分惨被教育.jpg)

出这题的本意其实是想看看有没有吊打std的做法的,笔者推了很久并没有找到线性的做法。

posted @   Refined_heart  阅读(604)  评论(3编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· .NET Core 中如何实现缓存的预热?
· 三行代码完成国际化适配,妙~啊~
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
点击右上角即可分享
微信分享提示