Codeforces 809E Surprise me! 莫比乌斯反演+点分治

题意

给一棵n个节点的数,点权为1到n的整数且互不相同。求 ∑ i = ̸ j φ ( v a l i ∗ v a l j ) ∗ d i s ( i , j ) n ∗ ( n − 1 ) \frac{\sum_{i=\not j}\varphi(val_i*val_j)*dis(i,j)}{n*(n-1)} n(n1)i≠jφ(valivalj)dis(i,j)
n ≤ 2 ∗ 1 0 5 n\le2*10^5 n2105

分析

注意到 φ ( a b ) = φ ( a ) ∗ φ ( b ) ∗ g c d ( a , b ) φ ( g c d ( a , b ) ) \varphi(ab)=\frac{\varphi(a)*\varphi(b)*gcd(a,b)}{\varphi(gcd(a,b))} φ(ab)=φ(gcd(a,b))φ(a)φ(b)gcd(a,b)
枚举 v a l i val_i vali v a l j val_j valj的最大公约数 d d d,设 f d = ∑ g c d ( v a l i , v a l j ) = d φ ( v a l i ) ∗ φ ( v a l j ) ∗ d i s ( i , j ) f_d=\sum_{gcd(val_i,val_j)=d}\varphi(val_i)*\varphi(val_j)*dis(i,j) fd=gcd(vali,valj)=dφ(vali)φ(valj)dis(i,j)
那么答案就是 a n s = ∑ d = 1 n f d ∗ d φ ( d ) ans=\sum_{d=1}^n\frac{f_d*d}{\varphi(d)} ans=d=1nφ(d)fdd
我们只要求出 g d = ∑ d ∣ g c d ( v a l i , v a l j ) φ ( v a l i ) ∗ φ ( v a l j ) ∗ d i s ( i , j ) g_d=\sum_{d|gcd(val_i,val_j)}\varphi(val_i)*\varphi(val_j)*dis(i,j) gd=dgcd(vali,valj)φ(vali)φ(valj)dis(i,j)
然后用反演来求 f d f_d fd即可。
g d g_d gd可以用点分治或者虚树,加入一个点的时候枚举其约数即可。
时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#define pb push_back

typedef long long LL;

const int N=200005;
const int MOD=1000000007;

int n,a[N],cnt,last[N],phi[N],mu[N],prime[N],tot,g[N],s1[N],s2[N],f[N],root,sum,size[N],top,stack[N],dep[N];
bool not_prime[N],vis[N];
struct edge{int to,next;}e[N*2];
std::vector<int> vec[N],rub;

int add(int x,int y) {return x+y<MOD?x+y:x+y-MOD;}
int dec(int x,int y) {return x-y<0?x-y+MOD:x-y;}
int mul(int x,int y) {return (LL)x*y%MOD;}

int ksm(int x,int y)
{
	int ans=1;
	while (y)
	{
		if (y&1) ans=(LL)ans*x%MOD;
		x=(LL)x*x%MOD;y>>=1;
	}
	return ans;
}

void addedge(int u,int v)
{
	e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt;
	e[++cnt].to=u;e[cnt].next=last[v];last[v]=cnt;
}

void get_prime(int n)
{
	mu[1]=phi[1]=1;
	for (int i=2;i<=n;i++)
	{
		if (!not_prime[i]) prime[++tot]=i,phi[i]=i-1,mu[i]=-1;
		for (int j=1;j<=tot&&i*prime[j]<=n;j++)
		{
			not_prime[i*prime[j]]=1;
			if (i%prime[j]==0)
			{
				phi[i*prime[j]]=phi[i]*prime[j];
				break;
			}
			mu[i*prime[j]]=-mu[i];
			phi[i*prime[j]]=phi[i]*(prime[j]-1);
		}
	}
}

void get_root(int x,int fa)
{
	size[x]=1;f[x]=0;
	for (int i=last[x];i;i=e[i].next)
	{
		if (e[i].to==fa||vis[e[i].to]) continue;
		get_root(e[i].to,x);
		size[x]+=size[e[i].to];
		f[x]=std::max(f[x],size[e[i].to]);
	}
	f[x]=std::max(f[x],sum-size[x]);
	if (!root||f[x]<f[root]) root=x;
}

void get(int x,int fa)
{
	stack[++top]=x;size[x]=1;dep[x]=dep[fa]+1;
	for (int i=last[x];i;i=e[i].next)
		if (e[i].to!=fa&&!vis[e[i].to]) get(e[i].to,x),size[x]+=size[e[i].to];
}

void solve(int x)
{
	vis[x]=1;
	for (int i=0;i<vec[a[x]].size();i++) s1[vec[a[x]][i]]=add(s1[vec[a[x]][i]],phi[a[x]]),rub.pb(vec[a[x]][i]);
	dep[x]=0;
	for (int i=last[x];i;i=e[i].next)
	{
		if (vis[e[i].to]) continue;
		top=0;
		get(e[i].to,x);
		for (int j=1;j<=top;j++)
			for (int k=0;k<vec[a[stack[j]]].size();k++)
			{
				int y=stack[j],p=vec[a[y]][k];
				rub.pb(p);
				g[p]=add(g[p],mul(phi[a[y]],add(s2[p],mul(s1[p],dep[y]))));
			}
		for (int j=1;j<=top;j++)
			for (int k=0;k<vec[a[stack[j]]].size();k++)
			{
				int y=stack[j],p=vec[a[y]][k];
				rub.pb(p);
				s1[p]=add(s1[p],phi[a[y]]);s2[p]=add(s2[p],mul(phi[a[y]],dep[y]));
			}
	}
	for (int i=0;i<rub.size();i++) s1[rub[i]]=s2[rub[i]]=0;
	rub.clear();
	for (int i=last[x];i;i=e[i].next)
	{
		if (vis[e[i].to]) continue;
		root=0;sum=size[e[i].to];
		get_root(e[i].to,x);
		solve(root);
	}
}

int main()
{
	scanf("%d",&n);
	get_prime(n);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	for (int i=1;i<=n;i++)
		for (int j=i;j<=n;j+=i)
			vec[j].pb(i);
	for (int i=1;i<n;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		addedge(x,y);
	}
	sum=n;root=0;
	get_root(1,0);
	solve(root);
	int ans=0;
	for (int i=1;i<=n;i++)
	{
		int s=0;
		for (int j=i;j<=n;j+=i) (s+=(LL)g[j]*mu[j/i]%MOD)%=MOD;
		(ans+=(LL)s*i%MOD*ksm(phi[i],MOD-2)%MOD)%=MOD;
	}
	ans=(ans*2%MOD+MOD)%MOD;
	printf("%d\n",(LL)ans*ksm((LL)n*(n-1)%MOD,MOD-2)%MOD);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值