GCD Counting题解

发布时间 2023-09-02 10:33:15作者: 傻阙的缺

题意

有一棵有 \(n\) 个节点的树,第 \(i\) 个节点有点权 \(a_i\)

定义 \(g(x,y)\)\(x\)\(y\) 的树上路径所经过的点的点权 \(\gcd\)

对于每一个正整数 \(k\in[1,2\times 10^5]\) 求出满足以下条件的 \(x,y\) 的对数:

  • \(1\le x\le y\le n\)

  • \(g(x,y)=k\)

\(1\le n,a_i\le 2\times 10^5\)

Trick 1

显然 \(gcd(x,y)=k\) 是非常难求的,所以我们考虑用莫比乌斯反演,令 \(F(i)\) 表示 \(gcd(x,y)=k\) 的点对个数 \(G(i)\) 表示 \(gcd(x,y)\)\(k\) 的倍数的点对个数。很明显,求出所有 \(G(i)\) 就可以求出所有 \(F(i)\),即:

\[F(i)=\sum\limits_{i|d}\mu(\frac{d}{i})G(d) \]

Trick 2

考虑求 \(G(i)\)。枚举两端的点权都是 \(i\) 的边,合并两个点,最后计算每个联通块内的点数,任意两个点都是一个合法的点对,所以是 \(x\times (x-1)/2\)\(x\) 为连通块中点的数量。每条边最多被算 \(160\) 次,即当 \(gcd(u,v)=166320=2^4×3^3×5×7×11\) 时,他有 \(160\) 个因数,他的每个因数都会枚举这条边,但它是小于 \(2e5\) 的拥有最多因数的数,所以枚举 \(G(i)\) 的时间复杂度不超过 \(O(160m)\)

Trick 3

考虑倒着求 \(F(i)\),很明显当求到 \(F(i)\) 时,所有满足 \(j>i\)\(F(j)\) 都已经求出来,那么就会有:

\[F(i)=G(i)-\sum\limits_{i|d}F(d) \]

就不用求 \(\mu\)

细节:

因为形如 \((i,i)\) 的点对是合法的,所以在输出答案 \(F(i)\) 时还要加上点权为 \(i\) 的点的个数

上代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+50;
int n,a[N],u[N],v[N],tzy;
vector<int> val[N];
int f[N],gs[N],f1,f2;
bool vis[N];
ll G[N];
int find(int x)
{
	if(f[x]==x) return x;
	return f[x]=find(f[x]);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	for(int i=1;i<n;i++)
	{
		scanf("%d %d",&u[i],&v[i]);
		tzy=__gcd(a[u[i]],a[v[i]]);
		for(int j=1;j*j<=tzy;j++)
		{
			if(tzy%j) continue;
			val[j].push_back(i);
			if(j^(tzy/j)) val[tzy/j].push_back(i);
		}
	}
	for(int i=1;i<=n;i++)
	f[i]=i,gs[i]=1;
	for(int i=1;i<=N-50;i++)
	{
		for(int j=0;j<val[i].size();j++)
		{
			f1=find(u[val[i][j]]),f2=find(v[val[i][j]]);
			if(f1!=f2)
			{
				G[i]=G[i]-1ll*gs[f1]*(gs[f1]-1)/2-1ll*gs[f2]*(gs[f2]-1)/2;
				f[f1]=f2;
				gs[f2]+=gs[f1];
				G[i]=G[i]+1ll*gs[f2]*(gs[f2]-1)/2;
			}
		}
		for(int j=0;j<val[i].size();j++)
		{
			f[u[val[i][j]]]=u[val[i][j]];
			f[v[val[i][j]]]=v[val[i][j]];
			gs[u[val[i][j]]]=1;
			gs[v[val[i][j]]]=1;
		}
	}
	for(int i=2e5;i>=1;i--)
	{
		for(int j=i+i;j<=2e5;j+=i) G[i]-=G[j];
	}
	for(int i=1;i<=n;i++) G[a[i]]++;
	for(int i=1;i<=2e5;i++) if(G[i]) printf("%d %lld\n",i,G[i]);
	return 0;
}