【洛谷P7854】GCD Tree

题目

题目链接: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;
}
原文地址:https://www.cnblogs.com/stoorz/p/15229013.html