【洛谷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_{\text{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;
}
posted @ 2021-09-05 11:52  stoorz  阅读(140)  评论(0编辑  收藏  举报