Codeforces809E - Surprise me!【欧拉函数变换 + 莫比乌斯反演 + 虚树】

题目描述:

洛谷
在这里插入图片描述
n ≤ 2 ∗ 1 0 5 n\le2*10^5 n2105

题目分析:

首先考虑怎么解决 φ ( i ∗ j ) \varphi(i*j) φ(ij)
我们希望的自然是把它变成 φ ( i ) ∗ φ ( j ) \varphi(i)*\varphi(j) φ(i)φ(j) 方便独立计算,那么观察这两个形式的差别:
φ ( i ) ∗ φ ( j ) = i ∗ j ∗ ∏ p ∣ i ( 1 − 1 p ) ∏ p ∣ j ( 1 − 1 p ) φ ( i ∗ j ) = i ∗ j ∗ ∏ p ∣ i j ( 1 − 1 p ) \begin{aligned}\varphi(i)*\varphi(j)&=i*j*\prod_{p|i}(1-\frac 1p)\prod_{p|j}(1-\frac 1p)\\\varphi(i*j)&=i*j*\prod_{p|ij}(1-\frac 1p)\end{aligned} φ(i)φ(j)φ(ij)=ijpi(1p1)pj(1p1)=ijpij(1p1)容易发现 φ ( i ) ∗ φ ( j ) \varphi(i)*\varphi(j) φ(i)φ(j) 多乘的是 g c d ( i , j ) gcd(i,j) gcd(i,j) 的质因子在 φ \varphi φ中的形式,我们把它写成 φ ( g c d ( i , j ) ) g c d ( i , j ) \varphi(gcd(i,j))\over gcd(i,j) gcd(i,j)φ(gcd(i,j))
于是 φ ( i ∗ j ) = φ ( i ) φ ( j ) g c d ( i , j ) φ ( g c d ( i , j ) ) \varphi(i*j)={\varphi(i)\varphi(j)gcd(i,j)\over\varphi(gcd(i,j))} φ(ij)=φ(gcd(i,j))φ(i)φ(j)gcd(i,j)


回到原题目中,我们枚举 g c d ( a i , a j ) = d gcd(a_i,a_j)=d gcd(ai,aj)=d,那么原式(去掉前面的 1 n ( n − 1 ) \frac 1{n(n-1)} n(n1)1)等于
∑ d = 1 n d φ ( d ) ∑ i = 1 n ∑ j = 1 n φ ( a i ) φ ( a j ) [ g c d ( a i , a j ) = = d ] d i s t ( a i , a j ) \sum_{d=1}^n{d\over \varphi(d)}\sum_{i=1}^n\sum_{j=1}^n\varphi(a_i)\varphi(a_j)[gcd(a_i,a_j)==d]dist(a_i,a_j) d=1nφ(d)di=1nj=1nφ(ai)φ(aj)[gcd(ai,aj)==d]dist(ai,aj)

f ( d ) = ∑ i = 1 n ∑ j = 1 n φ ( a i ) φ ( a j ) [ g c d ( a i , a j ) = = d ] d i s t ( a i , a j ) f(d)=\sum_{i=1}^n\sum_{j=1}^n\varphi(a_i)\varphi(a_j)[gcd(a_i,a_j)==d]dist(a_i,a_j) f(d)=i=1nj=1nφ(ai)φ(aj)[gcd(ai,aj)==d]dist(ai,aj)

这个 [ g c d ( a i , a j ) = = d ] [gcd(a_i,a_j)==d] [gcd(ai,aj)==d]的限制可以利用莫比乌斯反演转化成计算所有 g c d gcd gcd等于 d d d的倍数的点对。
即计算 F ( d ) = ∑ d ∣ x f ( x ) F(d)=\sum_{d|x}f(x) F(d)=dxf(x)。而 F ( d ) = ∑ d ∣ a i ∑ d ∣ a j φ ( a i ) φ ( a j ) d i s t ( a i , a j ) F(d)=\sum_{d|a_i}\sum_{d|a_j}\varphi(a_i)\varphi(a_j)dist(a_i,a_j) F(d)=daidajφ(ai)φ(aj)dist(ai,aj)这个可以将 d i s t ( a i , a j ) dist(a_i,a_j) dist(ai,aj)拆开后用虚树DP快速计算。因为 a i a_i ai的权值是排列,所以总点数是 O ( n ln ⁡ n ) O(n\ln n) O(nlnn)的。
然后 f ( d ) = ∑ d ∣ x F ( x ) ∗ μ ( x d ) f(d)=\sum_{d|x}F(x)*\mu(\frac xd) f(d)=dxF(x)μ(dx)
最后再乘上 1 n ( n − 1 ) \frac 1{n(n-1)} n(n1)1就可以了。总复杂度 O ( n ln ⁡ n ) O(n\ln n) O(nlnn)

Code:

#include<bits/stdc++.h>
#define maxn 200005
using namespace std;
const int mod = 1e9+7;
int n,a[maxn],pos[maxn],q[maxn],sz,mu[maxn],phi[maxn],ans,F[maxn];
int Pow(int a,int b){int s=1;for(;b;b>>=1,a=1ll*a*a%mod) b&1&&(s=1ll*s*a%mod); return s;}
void Pre(const int N){
	phi[1]=mu[1]=1; static int p[maxn],cnt=0; static bool v[maxn];
	for(int i=2;i<=N;i++){
		if(!v[i]) p[++cnt]=i,phi[i]=i-1,mu[i]=-1;
		for(int j=1,k;j<=cnt&&(k=i*p[j])<=N;j++){
			v[k]=1; if(i%p[j]==0) {phi[k]=phi[i]*p[j];break;} 
			phi[k]=phi[i]*(p[j]-1),mu[k]=-mu[i];
		}
	}
}
const int Log = 18;
int dfn[maxn],st[19][maxn*2],lg[maxn*2],dep[maxn],idx;
int fir[maxn],nxt[maxn<<1],to[maxn<<1],tot;
void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
void dfs(int u,int ff){
	dep[u]=dep[ff]+1,st[0][++idx]=u,dfn[u]=idx;
	for(int i=fir[u],v;i;i=nxt[i]) if((v=to[i])!=ff)
		dfs(v,u),st[0][++idx]=u;
}
int LCA(int x,int y){
	if((x=dfn[x])>(y=dfn[y])) swap(x,y);
	int k=lg[y-x+1];
	return dep[st[k][x]]<dep[st[k][y-(1<<k)+1]]?st[k][x]:st[k][y-(1<<k)+1];
}
namespace VTree{
	int ret,k,sum[maxn],S[maxn],tp;
	bool kp[maxn];
	bool cmp(int i,int j){return dfn[i]<dfn[j];}
	void dfs2(int u){
		if(kp[u]) sum[u]=phi[a[u]],ret=(ret+1ll*phi[a[u]]*phi[a[u]]*dep[u])%mod;
		for(int i=fir[u],v;i;i=nxt[i])
			dfs2(v=to[i]),ret=(ret+2ll*sum[u]*sum[v]%mod*dep[u])%mod,sum[u]=(sum[u]+sum[v])%mod;
	}
	int solve(){
		sort(q+1,q+1+sz,cmp),k=sz,ret=0;
		for(int i=1;i<=k;i++) kp[q[i]]=1;
		for(int i=(S[tp=1]=q[1],2);i<=k;i++){
			int lca=LCA(S[tp],q[i]);
			for(;dfn[lca]<=dfn[S[tp-1]];tp--) line(S[tp-1],S[tp]);
			if(lca!=S[tp]) line(lca,S[tp]),S[tp]=q[++sz]=lca;
			S[++tp]=q[i];
		}
		for(;tp>1;tp--) line(S[tp-1],S[tp]);
		dfs2(S[1]),ret=-ret;
		for(int i=1;i<=k;i++) ret=(ret+1ll*phi[a[q[i]]]*dep[q[i]]%mod*sum[S[1]])%mod;
		for(tot=0;sz;sz--) fir[q[sz]]=sum[q[sz]]=kp[q[sz]]=0;
		return ret*2%mod;
	}
}
int main()
{
	scanf("%d",&n);
	Pre(n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),pos[a[i]]=i;
	for(int i=1,x,y;i<n;i++) scanf("%d%d",&x,&y),line(x,y),line(y,x);
	dfs(1,0),lg[0]=-1;
	for(int i=1;i<=idx;i++) lg[i]=lg[i>>1]+1;
	for(int j=1;j<=Log;j++) for(int i=1,l=1<<j;i+l-1<=idx;i++) 
		st[j][i]=dep[st[j-1][i]]<dep[st[j-1][i+(l>>1)]]?st[j-1][i]:st[j-1][i+(l>>1)];
	memset(fir,0,sizeof fir),tot=0;
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j+=i) q[++sz]=pos[j];
		F[i]=VTree::solve();
	}
	for(int i=1;i<=n;i++){
		int now=0;
		for(int j=i;j<=n;j+=i) now=(now+F[j]*mu[j/i])%mod;
		ans=(ans+1ll*i*Pow(phi[i],mod-2)%mod*now)%mod;
	}
	printf("%d\n",((1ll*ans*Pow(1ll*n*(n-1)%mod,mod-2))%mod+mod)%mod);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值