【题解】NKOJ/【BZOJ2790】【Poi2012】距离——数学

问题描述

对于两个正整数a、b,这样定义函数d(a,b):每次操作可以选择一个质数p,将a变成a*p或a/p,
如果选择变成a/p就要保证p是a的约数,d(a,b)表示将a变成b所需的最少操作次数。
例如d(69,42)=3。
现在给出n个正整数A1,A2,…,An,对于每个i (1<=i<=n),求最小的j(1<=j<=n)使得i≠j且d(Ai,Aj)最小。

输入格式
第一行一个正整数n (2<=n<=100,000)。
第二行n个正整数A1,A2,…,An (Ai<=1,000,000)。

数的唯一分解定理得一个数只有唯一的质因数分解方案,加上题目是只能用质数进行操作,所以一个很显然的结论:
Cnt[x]:x被分解质因数后,各个质因数的指数和。
d(a,b)=Cnt[a]+Cnt[b]-2*Cnt[gcd(a,b)];(想一想,为什么)

所以你激动地写下了如下代码:

#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define H 1000005
template<typename T>
void R(T &t){t=0;int k=1;char c=getchar();while(!isdigit(c)){if(c=='-')k=-1;c=getchar();}while(isdigit(c)){t=t*10+c-'0';c=getchar();}t*=k;}

LL N,Max,cnt,A[H/10],Cnt[H],Prime[H],Ans[H],Res[H];
bool MK[H];
void Ready(LL Max){
	Cnt[1]=0;
	for(int i=2;i<=Max;i++){
		if(!MK[i])Prime[++cnt]=i,Cnt[i]=1;
		for(int j=1;j<=cnt&&i*Prime[j]<=Max;j++){
			MK[i*Prime[j]]=1;Cnt[i*Prime[j]]=Cnt[i]+1;
			if(!i%Prime[j])break;
		}
	}
}
LL gcd(LL a,LL b){
	return b==0?a:gcd(b,a%b);
}
int main(){
	R(N);for(int i=1;i<=N;i++){R(A[i]);Max=max(Max,A[i]);}
	Ready(Max);
	for(int i=1;i<=N;i++)Ans[i]=1234567890;
	for(int i=1;i<=N;i++){
		for(int j=1;j<=N;j++){
			if(i==j)continue;
			LL Now=Cnt[A[i]]+Cnt[A[j]]-2*Cnt[gcd(A[i],A[j])];
			if(Ans[i]>Now)Ans[i]=Now,Res[i]=j;
		}
	}
	for(int i=1;i<=N;i++)printf("%lld\n",Res[i]);
}

然后很愉快的T了。算一算时间复杂度为 O O O(n2 l o g log log A i A_i Ai)爆到飞起。
看看有什么可以优化的。
观察发现,式子 Cnt[A[i]]+Cnt[A[j]]-2*Cnt[gcd(A[i],A[j])] 中对于每讨论到一个A[i],Cnt[A[i]]是一个定值。
我们只需要让Cnt[A[j]]-2*Cnt[gcd(A[i],A[j])] 最小即可。
如果我们暴力讨论j的位置的话,显然会超时。
而如果我们枚举gcd(A[i],A[j]) 时间复杂度就会降到 O ( n A i ) O(n\sqrt A_i) O(nA i) .

代码如下

/*
	Cnt[x]:x被分解质因数后,各个质因数的指数和; 
	D(a,b)=Cnt[a]+Cnt[b]-2*Cnt[ gcd(a,b) ];
	M1[i]:i的倍数A[j]中,Cnt[A[j]]最小的的A[j]下标j;
	M2[i]:同上,只不过变为次小值;
*/ 
#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define H 1000100
#define inf 0x3f3f3f3f3f3f3f
template<typename T>
void R(T &t){t=0;int k=1;char c=getchar();while(!isdigit(c)){if(c=='-')k=-1;c=getchar();}while(isdigit(c)){t=t*10+c-'0';c=getchar();}t*=k;}
LL N,Max,cnt,A[H/10],Cnt[H],Prime[H],M1[H],M2[H];
bool MK[H];
void Check(int x,int y){
	if(Cnt[A[x]]<=Cnt[A[M1[y]]])M2[y]=M1[y],M1[y]=x;
	else if(Cnt[A[x]]<=Cnt[A[M2[y]]])M2[y]=x;
}
void Ready(LL Max){
	Cnt[0]=inf;
	for(int i=2;i<=Max;i++){
		if(!MK[i])Prime[++cnt]=i,Cnt[i]=1;
		for(int j=1;j<=cnt&&i*Prime[j]<=Max;j++){
			MK[i*Prime[j]]=1;Cnt[i*Prime[j]]=Cnt[i]+1;
			if(i%Prime[j]==0)break;
		}
	}//筛质数,预处理每个数的质因数的指数和
	for(int i=N;i>=1;i--){//从后往前枚举,保证在值相同的情况下,位置最小 
		for(int j=1;j*j<=A[i];j++){//枚举A[i]的因数 
			if(A[i]%j==0){
				Check(i,j);
				if(j*j!=A[i])Check(i,A[i]/j);
			}
		}
	}//预处理 M1数组 与 M2数组 
}
LL Work(int x){
	LL Ans=inf,Pos=inf,Now;
	for(int K=1;K*K<=A[x];K++){//A[x]的因数 
		if(A[x]%K==0){
			int j,k=K;
			if(M1[k]!=x)j=M1[k];else j=M2[k];
			Now=Cnt[A[j]]-2*Cnt[k];
			if(Now<Ans||(Now==Ans&&j<Pos))Ans=Now,Pos=j;
			if(K*K!=A[x]){
				k=A[x]/K;
				if(M1[k]!=x)j=M1[k];else j=M2[k];
				Now=Cnt[A[j]]-2*Cnt[k];
				if(Now<Ans||(Now==Ans&&j<Pos))Ans=Now,Pos=j;
			}
		}
	}
	return Pos;
}
int main(){
	R(N);for(int i=1;i<=N;i++){R(A[i]);Max=max(Max,A[i]);}
	Ready(Max);
	for(int i=1;i<=N;i++)printf("%lld\n",Work(i));
}

PS:(看了以下这段文字你可能会被我带晕,请谨慎阅读)

Q:你为什么要存最小值和次小值啊?
A:因为当你讨论到A[i]的时候会有M1[k]==i的情况啊。

Q:你这个程序有问题啊: 对于A[i],你枚举它的因数K,然后通过M数组得到A[j]。你只能保证A[j]是K的倍数,你并不能保证K是A[i]和A[j]的最大公约数啊。这样你的式子就不成立了呀!
A: 好问题!
   但仔细想想,你会发现这不会对真正的答案造成影响。
   因为你枚举的K所对应的A[j],其与A[i]的最大公约数 ≥ \geq K,
   那么对于式子Cnt[A[j]]-2*Cnt[gcd(A[i],A[j])]Cnt[A[j]]-2*Cnt[K] 来说 后者的值肯定 ≥ \geq 前者,所以不会更新最小值。

Q: 那对于那些A[k] (k!=j,k!=i)的值中与A[i]的最大公约数真正为K的不是没有讨论到吗?
A: 对于那些A[k],其Cnt[gcd(A[i],A[k])]Cnt[K]相同,但是Cnt[A[k]]>Cnt[A[j] 所以没有A[j]优,不讨论。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

go_bananas

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值