计蒜客 青云的机房组网方案

计蒜客 青云的机房组网方案

题目链接:青云的机房组网方案(困难) – 题库 – 计蒜客 (jisuanke.com)


​ 注意到,\(a_i\) 的范围为 \([1,10^5]\),又由于 \(2\times3\times5\times7\times11\times13=30030\),所以 \(a\) 的质因数的种类最多只有 \(6\) 种。

​ 然后我们考虑通过容斥计算答案。答案等于所有点对之间的距离减去不互质点对之间的距离

​ 所有点对之间的距离可以用换根 dp 在 \(O(n)\) 的时间复杂度内解决。

​ 那么问题就转换成了不互质点对间的距离

​ 设现在的要解决的点为 \(rt\) ,我们以 \(rt\) 为根遍历整颗树。拿一个桶 \(d_i\) 表示所有点权包含 \(i\) 这个质因数的深度总和。然后对于 \(rt\) 我们不记录,dfs 完之后我们用 \(2^6\) 的时间容斥计算出所有与 \(rt\) 不互质的点到 \(rt\) 的距离。

​ 这样我们做 \(n\) 遍,那么就出答案了。但是这样的复杂度是 \(O(n^2\times2^6)\)

​ 注意到题目中并没有给出固定的根,也就是这题没有一个会对答案造成影响的给出的根。那么我们套个点分治就好了。

​ 总时间复杂度 \(O(n\log n\times2^6)\)

代码如下:

#include<bits/stdc++.h>
#define ll long long
#define lowbit(i) (i&(-i))
using namespace std;
const int MAXN = 1e5+5;
const int MX = 1e5;
int n,a[MAXN],siz[MAXN];
ll ans,f[MAXN];
vector <int> e[MAXN],fac[MAXN];
void pre_dfs(int p,int fa)
{
	siz[p]=1;f[p]=0;
	for(int i=0;i<e[p].size();++i)
	{
		int to=e[p][i];
		if(to==fa) continue;
		pre_dfs(to,p);
		f[p]+=f[to]+siz[to];
		siz[p]+=siz[to];
	}
}
void dfs(int p,int fa)
{
	ans+=f[p];
//	cout<<p<<" "<<f[p]<<endl;
	for(int i=0;i<e[p].size();++i)
	{
		int to=e[p][i];
		if(to==fa) continue;
		f[to]=f[p]+1ll*siz[p]-2ll*siz[to];
		siz[to]=siz[p];
		dfs(to,p);
	}
}
bool vis[MAXN];
int dp[MAXN],Tsiz,root,numb[MAXN][(1<<6)+5],cnt_1[(1<<6)+5],cnt;
ll d[MAXN],cnt_num[MAXN];
void Get_root(int p,int fa)
{
	dp[p]=0;siz[p]=1;
	for(int i=0;i<e[p].size();++i)
	{
		int to=e[p][i];
		if(to==fa||vis[to]) continue;
		Get_root(to,p);
		siz[p]+=siz[to];
		dp[p]=max(dp[p],siz[to]);
	}
	dp[p]=max(dp[p],Tsiz-siz[p]);
	if(dp[p]<dp[root]) root=p;
}
struct node
{
	int p,dis;
}arr[MAXN];
void Get_dis(int p,int d,int fa)
{
	arr[++cnt]=node{p,d};
	for(int i=0;i<e[p].size();++i)
	{
		int to=e[p][i];
		if(to==fa||vis[to]) continue;
		Get_dis(to,d+1,p);
	}
}
void solve(int p)
{
	for(int i=0;i<e[p].size();++i)
	{
		int to=e[p][i];
		if(vis[to]) continue;
		cnt=0;Get_dis(to,1,p);
		for(int j=1;j<=cnt;++j)
		{
			int now=arr[j].p,val=arr[j].dis;
			for(int k=1;k<(1<<fac[now].size());++k)
			{
				int x=numb[now][k];
				int cnt1=cnt_1[k];
				if(cnt1&1) ans-=cnt_num[x]*val+d[x];
				else ans+=cnt_num[x]*val+d[x];
			}
		}
		for(int j=1;j<=cnt;++j)
		{
			int now=arr[j].p,val=arr[j].dis;
			for(int k=1;k<(1<<fac[now].size());++k)
			{
				int x=numb[now][k];
				d[x]+=val;
				cnt_num[x]++;
			}
		}
	}
	for(int k=1;k<(1<<fac[p].size());++k)
	{
		int x=numb[p][k];
		int cnt1=cnt_1[k];
		if(cnt1&1) ans-=d[x];
		else ans+=d[x];
	}
	for(int i=0;i<e[p].size();++i)
	{
		int to=e[p][i];
		if(vis[to]) continue;
		cnt=0;Get_dis(to,1,p);
		for(int j=1;j<=cnt;++j)
		{
			int now=arr[j].p,val=arr[j].dis;
			for(int k=1;k<(1<<fac[now].size());++k)
			{
				int x=numb[now][k];
				d[x]=0;
				cnt_num[x]=0;
			}
		}
	}
}
void Divide(int p)
{
	vis[p]=1;
	solve(p);
	for(int i=0;i<e[p].size();++i)
	{
		int to=e[p][i];
		if(vis[to]) continue;
		root=0;Tsiz=siz[to];
		Get_root(to,p);
		Divide(root);
	}
}
int lg[(1<<6)+5];
int main()
{
	freopen("network.in","r",stdin);
	freopen("network.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	for(int i=1;i<=(1<<6);++i) lg[i]=lg[i-1]+(1<<lg[i-1]==i);
	for(int i=1;i<=n;++i)
	{
		int num=a[i];
		int up=sqrt(num)+1;
		for(int x=2;x<=up;++x)
		{
			if(num%x==0)
			{
				fac[i].push_back(x);
				while(num%x==0) num/=x;
			}
		}
		if(num!=1) fac[i].push_back(num);
		numb[i][0]=1;
		for(int j=1;j<(1<<fac[i].size());++j)
		{
			int k=lowbit(j);
			numb[i][j]=numb[i][j-k]*fac[i][lg[k]-1];
		}
	}
	for(int i=0;i<(1<<6);++i)
		cnt_1[i]=__builtin_popcount(i);
	for(int i=1;i<n;++i)
	{
		int u,v;
		scanf("%d %d",&u,&v);
		e[u].push_back(v);
		e[v].push_back(u);
	}
	pre_dfs(1,0);
	dfs(1,0);
	ans/=2;
	dp[root=0]=n+1;Tsiz=n+1;
	Get_root(1,0);
	Divide(root);
	printf("%lld\n",ans);
	return 0;
}

​ 简单的淀粉质。