问题描述
对于两个正整数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(nAi) .
代码如下
/*
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]优,不讨论。