POI 2012 ODL-Distance
给你一个序列a[],定义d(i,j)=a[i],a[j]每次操作可以对其中之一乘一个质数p或除以一个数p(p必须为被除数的约数),让a[i]=a[j]的最少操作步数
对于每个i,求d(i,j)最小的j,若有多个解,输出最小的j
题解:
其实这道题的性质啥的还是比较好推导的,我觉得难点只在于如何简洁、正确地实现代码。
先考虑:对于任意的两个数(A,B),如何用最少的乘除质数的操作完成转换?
肯定秒想质因子分解啊。
现在我们有A的质因子,B的质因子,A,B的公共质因子。明显地,最少步数就是相同的质因子保留,不同的质因子乘除,设(f(x))为(x)的质因子个数(质因子指数和)。那么最少步骤就是:(f(A)+f(B)-2f(gcd(A,B)))。
这个式子还是很好推导的。
然后显而易见的暴力思路就是对于每个位置(i),从头到尾枚举(j),一旦符合条件就跳出。(O(n^2))做法。
肯定是不行,我们思考优化。对于这种三元问题,定一元,枚举另一元,不行的话就枚举下一元好了。既然枚举(j)要T,我们尝试枚举(gcd(a[i],a[j])=g)。
那么对于(g),我们需要在它的所有倍数中找到存在于序列中的且下标尽可能小的位置。这个步骤不能用正向思维,比如用set维护。要逆向想:在枚举一个数(x)的所有因子(g)的过程中,(x)就是(g)的一个倍数,我们直接用(x)来更新就好了。
需要注意的是,有可能存在最小下标就是这个数本身的情况,于是我们还需要维护一个次小值。
最后查询的时候直接(O(1))就好。
代码:
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=1e6+5;
const int maxw=1e6+6;
const int INF=1e9;
int n,tot,ans,val,maxx;
int a[maxn],prime[maxn],cnt[maxn],mn1[maxn],mn2[maxn];
//mn1,mn2表示一个数的所有倍数中最小、次小值的位置
bool v[maxn];
void init(int x)
{
for(int i=2;i<=x;i++)
{
if(!v[i])
prime[++tot]=i,cnt[i]=1;
for(int j=1;j<=tot && i*prime[j]<=x;j++)
{
v[i*prime[j]]=1;
cnt[i*prime[j]]=cnt[i]+1;
if(i%prime[j]==0)
break;
}
}
}
void update(int d,int x)
{
if(cnt[a[x]]<cnt[a[mn1[d]]])
mn2[d]=mn1[d],mn1[d]=x;
else if(cnt[a[x]]<cnt[a[mn2[d]]]&&x!=mn1[d])
mn2[d]=x;
}
void getans(int d,int x)
{
int pos=mn1[d]==x?mn2[d]:mn1[d];
int v=cnt[a[x]]+cnt[a[pos]]-2*cnt[d];
if(v<val||(v==val&&pos<ans))
val=v,ans=pos;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
maxx=max(maxx,a[i]);
}
init(maxx);
cnt[0]=INF;
for(int i=1;i<=n;i++)
for(int j=1;j*j<=a[i];++j)
{
if(a[i]%j)
continue;
update(j,i);
update(a[i]/j,i);
}
for(int i=1;i<=n;++i)
{
val=INF;
for(int j=1;j*j<=a[i];++j)
{
if(a[i]%j)
continue;
getans(j,i);
getans(a[i]/j,i);
}
printf("%d
",ans);
}
return 0;
}