【题解】 CF809E Surprise me! 虚树+莫比乌斯反演+狄利克雷卷积

Legend

我们认为树上不同两点 (i,j) 的贡献是 (varphi(a_i imes a_j)dist(i,j)),求随机选两个不同点的期望贡献。

(10^9+7),结点 (le 2 imes 10^5),保证权值是个排列。

Link ( extrm{to Codeforces})

Editorial

第一步不难想到,要把 (a_i)(a_j) 分离,怎么分离呢?发现一个式子:(varphi(xy)=frac{varphi(x)varphi(y)gcd(x,y)}{varphi(gcd(x,y))})

这个式子怎么来的呢?你根据每个质因数考虑一下,就觉得非常有道理了。然后就是要求一个这样的式子:

[egin{aligned} & sum_{i=1}^{n}sum_{j=1}^{n} frac{varphi(a_i) varphi(a_j)gcd(a_i,a_j)}{varphi(gcd(a_i,a_j))}dist(i,j) \ =& sum_{d=1}^{n}frac{d}{varphi(d)} sum_{i=1}^{n}sum_{j=1}^nvarphi(a_i) varphi(a_j) dist(i,j)[gcd(a_i,a_j)=d] \ =& sum_{d=1}^{n} frac{d}{varphi(d)}sum_{i=1}^{n}sum_{j=1}^{n} varphi(i)varphi(j)[gcd(i,j)=d] imes dist(pos_i,pos_j) \ =& sum_{d=1}^{n} frac{d}{varphi(d)} sum_{i=1}^{lfloor frac{n}{d} floor} sum_{j=1}^{lfloor frac{n}{d} floor} varphi(id)varphi(jd)[gcd(i,j)=1] imes dist(pos_{id},pos_{jd}) \ =& sum_{d=1}^{n}frac{d}{varphi(d)} sum_{t=1}^{lfloor frac{n}{d} floor} mu(t)sum_{i=1}^{lfloor frac{n}{td} floor}sum_{j=1}^{lfloor frac{n}{td} floor} varphi(tid)varphi(tjd) imes dist(pos_{tid},pos_{tjd}) \ =& sum_{T=1}^{n} left( sum_{i=1}^{lfloor frac{n}{T} floor}sum_{j=1}^{lfloor frac{n}{T} floor}varphi(Ti)varphi(Tj) imes dist(pos_{Ti},pos_{Tj}) ight) imes left( sum_{d|T}frac{dmu(frac{T}{d})}{varphi(d)} ight) end{aligned} ]

前面这一截,可以解释为树上两点贡献为 (varphi(a_i)varphi(a_j)dist(i,j)),后面就是个狄利克雷卷积(显然这三个都是积性函数)。

手玩一下 (F(n)=sum_{d|n}frac{dmu(frac{n}{d})}{varphi(d)}) 这个函数在质数的幂下的取值。

(k) (0) (1) (2) (3)
(F(p^k)) (1) (frac{1}{p-1}) (0) (0)

发现出现平方因子的时候答案就是 (0),质数时为 (frac{1}{p-1})(1) 时为 (1)。所以可以直接卷了

显然,前面这一截直接快排建虚树按每条边算贡献,是 (O(nlog^2 n)) 的。

然后就做完了,感觉是个缝合题。。

Code

然后你就会发现代码巨长。怎么当年 cf 还能过审这种垃圾题啊

#include <bits/stdc++.h>

#define debug(...) fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)
	freopen(#x".in" ,"r" ,stdin);
	freopen(#x".out" ,"w" ,stdout)
#define LL long long

using namespace std;

const int MX = 4e5 + 23;
const LL MOD = 1e9 + 7;

int read(){
	char k = getchar(); int x = 0;
	while(k < '0' || k > '9') k = getchar();
	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
	return x;
}

LL qpow(LL a ,LL b ,LL p = MOD){
	LL ans = 1;
	while(b){if(b & 1) ans = ans * a % p;
		a = a * a % p ,b >>= 1;
	}return ans;
}

int head[MX] ,tot = 1;
struct edge{
	int node ,next;
}h[MX << 1];
void addedge(int u ,int v ,int flg = 1){
	h[++tot] = (edge){v ,head[u]} ,head[u] = tot;
	if(flg) addedge(v ,u ,false);
}

namespace FOR_LCA{
	int lg2[MX << 1];
	// void GetFA(int x); // Get father array of Tree <2>
	int rmq(int x ,int y);
	int LCA(int x ,int y); // Get LCA(x ,y) of Tree <2>
	int dfn[MX] ,cnt ,seq[20][MX << 1] ,seqcnt ,refer[MX];
	int dep[MX];
	int firstOccurrence[MX];
	void init_log2(){
		lg2[0] = -1;
		for(int i = 1 ; i < MX * 2 ; ++i)
			lg2[i] = lg2[i - 1] + (i == (i & -i));
		for(int i = 1 ; i <= 19 ; ++i){
			for(int j = 1 ; j + (1 << (i - 1)) - 1 <= seqcnt ; ++j){
				seq[i][j] = std::min(seq[i - 1][j] ,seq[i - 1][j + (1 << (i - 1))]);
			}
		}
	}
	void GetEuler(int x ,int f ,int depth){
		seq[0][++seqcnt] = dfn[x] = ++cnt;
		firstOccurrence[x] = seqcnt;
		refer[cnt] = x;
		dep[x] = depth;
		for(int i = head[x] ,d ; i ; i = h[i].next){
			if((d = h[i].node) == f) continue;
			GetEuler(d ,x ,depth + 1);
			seq[0][++seqcnt] = dfn[x];
		}
	}
	int rmq(int l ,int r){
		if(l > r) std::swap(l ,r);
		int len = lg2[r - l + 1];
		return std::min(seq[len][l] ,seq[len][r - (1 << len) + 1]);
	}
	int LCA(int l ,int r){return refer[rmq(firstOccurrence[l] ,firstOccurrence[r])];}
	int dis(int x ,int y){return dep[x] + dep[y] - 2 * dep[LCA(x ,y)];}
	void Main(){
		GetEuler(1 ,0 ,1);
		init_log2();
	}
}
using FOR_LCA::LCA;
using FOR_LCA::dis;

LL F[MX] ,phi[MX];
int npri[MX] ,pri[MX] ,qwq;
void init(){
	npri[0] = npri[1] = 1 ,phi[1] = F[1] = 1;
	for(int i = 2 ; i < MX ; ++i){
		if(!npri[i]){
			pri[++qwq] = i;
			F[i] = qpow(i - 1 ,MOD - 2);
			phi[i] =  i - 1;
		}
		for(int j = 1 ; j <= qwq && pri[j] * i < MX ; ++j){
			int aim = pri[j] * i;
			npri[aim] = 1;
			if(i % pri[j] == 0){
				phi[aim] = phi[i] * pri[j];
				F[aim] = 0;
				break;
			}
			phi[aim] = phi[i] * phi[pri[j]];
			F[aim] = F[i] * F[pri[j]] % MOD;
		}
	}
}

int n ,a[MX] ,pos[MX];

int app[2][MX] ,appcnt;
void GetOrder(int x ,int f){
	app[0][x] = ++appcnt;
	for(int i = head[x] ,d ; i ; i = h[i].next){
		if((d = h[i].node) == f) continue;
		GetOrder(d ,x);
	}
	app[1][x] = ++appcnt;
}

struct POINT{
	int id ,tim;
	bool operator <(const POINT& B)const{
		return tim < B.tim;
	}
}S[MX << 1];

int Suse[MX] ,Scnt;
void addPOINT(int x){
	if(Suse[x]++) return;
	++Scnt ,S[Scnt] = (POINT){x ,app[0][x]};
	++Scnt ,S[Scnt] = (POINT){-x ,app[1][x]};
}

int stk[MX] ,stktop;
LL size[MX]; 
LL solve(int T){
	LL all = 0;
	for(int i = T ; i <= n ; i += T){
		addPOINT(pos[i]);
		all = (all + phi[i]) % MOD;
	}
	std::sort(S + 1 ,S + 1 + Scnt);
	int curScnt = Scnt;
	for(int i = 1 ; i < curScnt ; ++i){
		addPOINT(LCA(abs(S[i].id) ,abs(S[i + 1].id)));
	}
	addPOINT(1);
	std::sort(S + 1 ,S + 1 + Scnt);

	LL ans = 0;
	for(int i = 1 ; i <= Scnt ; ++i){
		int x = abs(S[i].id);
		if(S[i].id > 0){
			stk[++stktop] = x;
			size[x] = (a[x] % T == 0) * phi[a[x]];
		}
		else{
			--stktop;
			if(stktop){
				// debug("%d
" ,stk[stktop]);
				(size[stk[stktop]] += size[x]) %= MOD;
				ans = (ans + (all - size[x] + MOD) * size[x] % MOD * dis(x ,stk[stktop])) % MOD;
			}
			Suse[x] = false;
		}
	}
	ans = ans * F[T] % MOD;

	Scnt = 0;
	return ans;
}

int main(){
	init();
	n = read();
	for(int i = 1 ; i <= n ; ++i) pos[a[i] = read()] = i;
	for(int i = 2 ,u ,v ; i <= n ; ++i){
		u = read() ,v = read();
		addedge(u ,v);
	}
	FOR_LCA::Main();
	GetOrder(1 ,0);
	LL ans = 0;
	for(int i = 1 ; i <= n / 2 ; ++i) ans = (ans + solve(i)) % MOD;
	ans = ans * qpow(1LL * n * (n - 1) % MOD ,MOD - 2) % MOD * 2 % MOD;
	printf("%lld
" ,ans);
	return 0;
}
原文地址:https://www.cnblogs.com/imakf/p/14365174.html