2020牛客暑期多校训练营(第一场)B:Infinite Tree(虚树)

2020牛客暑期多校训练营(第一场)B:Infinite Tree(虚树)_第1张图片


codeforce 上有一个 m = 5000 m = 5000 m=5000 的版本

和式相当于求这棵树的关键点的带权重心,由于树是无限大的,不能用一般的方法去求关键点的重心。
注意到重心一定落在关键点上,而关键点只有 1 0 5 10^5 105 个,可以建立虚树,然后使用二次扫描换根法来求解重心。

建立虚树需要知道关键点的 dfs 序的大小关系。若在递归时,优先走质因子较小的树边,那么容易证明对于任意的 i ∈ [ 1 , n ) i \in[1,n) i[1,n),恒有 d f n [ ( i + 1 ) ! ] > d f n [ i ! ] dfn[(i + 1)!] > dfn[i!] dfn[(i+1)!]>dfn[i!] 成立。

证明如下:设 i ! i! i! 的质因子分解形式为: p 1 x 1 ∗ p 2 x 2 ∗ . . ∗ p n x n , ( p 1 < p 2 < . . < p n ) p_1^{x_1}*p_2^{x_2}*..*p_n^{x_n},(p_1 < p_2 < ..p1x1p2x2..pnxn(p1<p2<..<pn) ( i + 1 ) ! (i + 1)! (i+1)! 会在增大某几项质因子的幂次(也可能会产生一个新的质因子 p n + 1 p_{n+1} pn+1),设从右至左第一个幂次不等的质因子为 p i p_i pi,则在 p i x i p_i^{x_i} pixi走完以后, i ! i! i! 会走 p i − 1 p_{i - 1} pi1 ( i + 1 ) ! (i + 1)! (i+1)! 会继续走 p i p_i pi,这使得 ( i + 1 ) ! (i + 1)! (i+1)! 的 dfs 序变大。当 ( i + 1 ) (i + 1) (i+1) 是一个质数时,第一个幂次不等的质因子为 p n + 1 p_{n + 1} pn+1,这种情况显然 dfs 序更小。

显然 d e p [ i ! ] = ∑ x i dep[i!] = \sum_{}x_i dep[i!]=xi,现在需要知道 d e p [ l c a ( i ! , ( i + 1 ) ! ) ] dep[lca(i!,(i + 1)!)] dep[lca(i!,(i+1)!)],根据定义它们的 l c a lca lca 显然为 p i x i ∗ p i + 1 x i + 1 ∗ . . . ∗ p n x n p_i^{x_i} * p_{i + 1}^{x_{i + 1}}*...*p_n^{x_n} pixipi+1xi+1...pnxn,当 i + 1 i + 1 i+1 是一个质数时,他们的 l c a lca lca 为 1

构建虚树的过程,就是利用栈维护一条链,出现新的 lca,这个 lca 不会再和其它点再求 lca,因此只需要处理出 i ! i! i! ( i + 1 ) ! (i + 1)! (i+1)! l c a lca lca 的深度,这个过程可以利用树状数组得到, d e p [ ( i + 1 ) ! ] dep[(i+1)!] dep[(i+1)!] 可以由 d e p [ i ! ] dep[i!] dep[i!] + d e p [ i + 1 ] dep[i + 1] dep[i+1] 得到。

最后对构建完的虚树二次扫描换根求解重心


代码:

#include 
using namespace std;
#define pii pair
#define fir first
#define sec second
#define lowbit(i) (i & (-i))
typedef long long ll;
const int maxn = 3e5 + 10;
const int N = 1e5;
int w[maxn], n, dep[maxn], lcadep[maxn];	//dep 维护每个点的深度, lcadep 维护每个点和上一个点的 lca 的深度 
int sta[maxn],top,tot,mindiv[maxn];			
int sum[maxn], son[maxn];
ll val[maxn], ans[maxn];
vector<int> g[maxn];
void add(int u,int v) {
	g[u].push_back(v);
	//g[v].push_back(u);
}
int getsum(int x) {
	int ans = 0;
	for (int i = x; i; i -= lowbit(i))
		ans += sum[i];
	return ans;
}
void upd(int x,int v) {
	for (int i = x; i <= N; i += lowbit(i))
		sum[i] += v;
}
void prework() {
	mindiv[1] = 0;
	for (int i = 2; i <= 100000; i++)
		for (int j = i; j <= 100000; j += i)
			if (!mindiv[j]) mindiv[j] = i;
}
void build(int n) {			//建立虚数
	//在这棵无限大的树,先遍历较小的质因子 ,则有 dfn[(i + 1)!] > dfn[i!]
	dep[1] = sta[top = 1] = 1;
	for (int i = 2, j; i <= n; i++) {
		dep[i] = dep[i - 1] + 1;			//加上最大质因子 
		for (j = i; j != mindiv[j]; j /= mindiv[j], dep[i]++);
		lcadep[i] = getsum(n) - getsum(j - 1) + 1;
		for (j = i; j != 1; j /= mindiv[j])
			upd(mindiv[j],1);
	}
	for (int i = 2; i <= n; i++) {
		while (top > 1 && dep[sta[top - 1]] >= lcadep[i]) {
			add(sta[top - 1],sta[top]);
			top--;
		}
		if (dep[sta[top]] != lcadep[i]) {
			dep[++tot] = lcadep[i]; w[tot] = 0;
			add(tot,sta[top]);
			sta[top] = tot;
		}
		sta[++top] = i;
	}
	while (top > 1)
		add(sta[top - 1],sta[top]), top--;
}
void dfs1(int u) {
	val[u] = w[u]; ans[u] = 0;
	for (auto it : g[u]) {
		dfs1(it);
		son[u] += son[it];
		val[u] += val[it];
		ans[u] += ans[it] + 1ll * val[it] * (dep[it] - dep[u]);
	}
} 
void dfs2(int u) {
	for (auto it : g[u]) {
		ll v = ans[u] - ans[it] - val[it] * (dep[it] - dep[u]);
		ll s = val[u] - val[it];
		ans[it] += v + s * (dep[it] - dep[u]);
		val[it] += s;
		dfs2(it);
	}
}
void clear() {
	for (int i = 2, j; i <= n; i++) {
		for (j = i; j != 1; j /= mindiv[j])
			upd(mindiv[j],-1);		
	}
}
int main() {
	prework();
	while (~scanf("%d",&n)) {
		tot = n;
		for (int i = 1; i <= n; i++)
			scanf("%d",&w[i]);
		build(n);
		dfs1(1); dfs2(1);
		ll res = 1e18;
		for (int i = 1; i <= tot; i++)
			res = min(res,ans[i]);
		for (int i = 1; i <= tot; i++)
			g[i].clear();
		clear();
		printf("%lld\n",res);
	}
	return 0;
}

你可能感兴趣的:(2020牛客暑期多校训练营(第一场)B:Infinite Tree(虚树))