题目
题目链接:https://www.luogu.com.cn/problem/P7854
给你 (n) 个点,编号分别为 (1,2,ldots,n),点权分别为 (a_1,a_2,ldots,a_n)。
请你用这 (n) 个点构造一棵树,使得 (forall 1 le i < j le n),(gcd(a_i, a_j) = a_{operatorname{lca}(i, j)})。
若无解,报告之,否则输出树的形态。
思路
首先可以把序列排序去重,相同点权的点全部连向其中一个就行了。下面所有点的编号都是点权。
对于一个点 (x),显然其子树内所有点都是 (x) 的倍数。同时所有 (x) 倍数的点都必须在 (x) 的子树内。
那么对于 (x) 的倍数 (y),如果 (y) 没有父节点,则把 (y) 的父节点设为 (z);如果 (y) 已经有父节点 (z) 了,且 (z) 不是 (x) 的倍数,那么显然无解;如果 (y) 的父节点 (z) 是 (x) 的倍数,那么只需要考虑 (z) 和 (x) 之间的连边,可以不用改 (y) 的父亲。
从大到小枚举 (x) 跑一遍就好了。
但是这样搞出来的数只能保证 (a_{ ext{lca}(i,j)}|gcd(a_i,a_j))。并不一定合法。也就是说,我们只需要把不合法情况判掉就行了。
下面的思路是我抄 QuantAsk 神仙的 /bx。
如果对于所有 ((i,j)) 都有存在权值为 (gcd(a_i,a_j)) 的点,那么我们的构造是一定合法的。且这个东西是充要的。
所以可以容斥一下求出每一个数作为 (gcd) 出现的次数,如果任意一个数 (x) 作为 (gcd) 出现过,但是没有权值为 (x) 的点,就无解了。
时间复杂度 (O((n+V)log V))。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1000010;
int n,m,a[N],b[N],fa[N],pos[N];
ll cnt[N];
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[i]=a[i]; cnt[a[i]]++;
if (!pos[a[i]]) pos[a[i]]=i;
}
for (int i=1;i<=1e6;i++)
for (int j=i*2;j<=1e6;j+=i)
cnt[i]+=cnt[j];
for (int i=1e6;i>=1;i--)
{
cnt[i]*=cnt[i];
for (int j=i*2;j<=1e6;j+=i)
cnt[i]-=cnt[j];
if (cnt[i] && !pos[i]) return printf("-1"),0;
}
sort(a+1,a+1+n);
m=unique(a+1,a+1+n)-a-1;
for (int i=m;i>=1;i--)
for (int j=a[i]*2;j<=1e6;j+=a[i])
if (pos[j])
{
if (!fa[j]) fa[j]=a[i];
if (fa[j]%a[i]) return printf("-1"),0;
}
for (int i=2;i<=m;i++)
if (!fa[a[i]]) return printf("-1"),0;
for (int i=1;i<=n;i++)
if (pos[b[i]]!=i) cout<<pos[b[i]]<<" ";
else cout<<pos[fa[b[i]]]<<" ";
return 0;
}