Loading

浅谈虚树

闲话
看到这篇博客的人教教我定语从句。

The book the cover of which is red is mine.

算法介绍

虚树简介

虚树处理用途较为单一,都是对一棵树上的若干关键点这一形式的题目进行的处理。一般情况下,当关键点很少的时候,虚树的用途就会很大。

假设对于一棵树上,有 \(k\) 个关键点,而我们只在乎关键点之间的路径构成的信息,并且一条路径上的信息比较容易处理,那我们就可以建出虚树。

虚树的关键优化点在于,他用与 \(k\) 相关的事件,把一条路径压缩成虚树上的一条边,达到缩小数据规模的目的。

虚树构建

考虑下面这一颗树。树的蓝色节点是关键点,树的红色节点是在虚树上但不是关键点的节点。

img

接下来证明一个引理。

引理:将关键节点按照 dfn 序排序,设 \(S\) 集合为所有关键节点与相邻两个关键节点 LCA 构成的点集,那么 \(S\) 集合与虚树点集相等。

证明
假设虚树点集为 $T$,有两个方向。
  • \(T\) 包含 \(S\)

反证,如果存在 \(x \in S\)\(x \notin T\) 的点 \(x\),显然 \(x\) 子树内存在两个关键点,这两个关键点的 \(LCA\) 等于 \(x\),也就是这两个点在 \(x\) 的不同儿子的子树里。比如下图中的两个紫点关键点和一个红点 \(x\)

image

显然,如果 \(x\) 不在虚树点集中的话,这两个关键点之间的祖先关系会错乱,矛盾。

  • \(S\) 包含 \(T\)

反证,假设存在 \(x \notin S\)\(x \in T\) 的点 \(x\),那么 \(x\) 子树内的所有关键点都属于同一个儿子的子树,这种点加入虚树,一定只有一个儿子,显然可以去除,不符合虚树点数最小的定义,矛盾。

因此,我们容易得到虚树的点集。考虑连边,我们对 \(S\) 点集按照 DFN 排序,然后从左往右扫。

假设当前扫到了点 \(p_i\),我们要找到 \(p_i\) 的父亲并与这个点连边。显然,这个点应该是 \(q\),其中 \(\operatorname{LCA}(p_i, q)\) 深度最大。(因为这个点是 \(q\) 最后一次与其他关键点发生分支,那接下来一定是一条链,可以压缩成虚树上的一条边)

容易发现 \(q = p_{i - 1}\),因此我们把 \(p_{i - 1}\)\(p_i\) 连边即可。

虚树代码

虚树代码
int build(vector<int> &s) {
	sort(s.begin(), s.end(), [&](int x, int y) {
		return dfn[x] < dfn[y];
	});
	vector<int> tmp; tmp.push_back(s[0]);
	for (int i = 1; i < (int) s.size(); i ++) {
		int c = getLCA(s[i - 1], s[i]);
		tmp.push_back(s[i]), tmp.push_back(c);
	}
	sort(tmp.begin(), tmp.end(), [&](int x, int y) {
		return dfn[x] < dfn[y];
	});
	int m = unique(tmp.begin(), tmp.end()) - tmp.begin();
	for (int i = 1; i < m; i ++) {
		int c = getLCA(tmp[i - 1], tmp[i]);
		re[c].push_back({tmp[i], d[tmp[i]] - d[c]});
		re[tmp[i]].push_back({c, d[tmp[i]] - d[c]});
	}
	s = tmp;
	return tmp[0];
}

例题讲解

P4103 大工程

题目描述
国家有一个大工程,要给一个非常大的交通网络里建一些新的通道。

我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上。

\(2\) 个国家 \(a,b\) 之间建一条新通道需要的代价为树上 \(a,b\) 的最短路径的长度。

现在国家有很多个计划,每个计划都是这样,我们选中了 \(k\) 个点,然后在它们两两之间 新建 \(\dbinom{k}{2}\) 条新通道(任意两点间都新建 \(1\) 条)。

现在对于每个计划,我们想知道:

  1. 这些新通道的代价和。
  2. 这些新通道中代价最小的是多少。
  3. 这些新通道中代价最大的是多少。

先建出虚树。我们设虚树上的一条边,权值为在原树上的路径长度,设 \(s_u\) 表示 \(u\) 在虚树上的子树大小。分别考虑:

  • 代价和:对于虚树上一个非根节电 \(u\),考虑 \(u \to fa_u\) 这条边。它贡献了 \(s_u \times (k - s_u)\) 次,贡献为 \(w \times s_u \times (k - s_u)\)

  • 代价最小:类似于 DP 求直径,设 \(mn_u\) 表示从 \(u\)\(u\) 子树内一关键点,这是很好算的,那对于每条路径,我们在 \(\text{LCA}\) 处通过 \(mn\) 数组计算贡献。

  • 代价最大:类似于 DP 求直径,设 \(mx_u\) 表示从 \(u\)\(u\) 子树内一关键点,这是很好算的,那对于每条路径,我们在 \(\text{LCA}\) 处通过 \(mx\) 数组计算贡献。

P4103 代码
/*******************************
| Author:  DE_aemmprty
| Remember:
    * Read the question carefully!!!!!!
    * If you don't make progress on a question within five minutes, it's very likely that you are going in the wrong direction.
    * Don't rush to write code. Take your time.
    * If Div.2D is not made, it indicates that you have been defrauded.
*******************************/
#include <bits/stdc++.h>
using namespace std;

template < typename T, typename...V > void chkMn(T &x, T y) { x = (x < y ? x : y); }
template < typename T, typename...V > void chkMx(T &x, T y) { x = (x < y ? y : x); }

long long read() {
    char c = getchar();
    long long x = 0, p = 1;
    while ((c < '0' || c > '9') && c != '-') c = getchar();
    if (c == '-') p = -1, c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return x * p;
}

const int N = 1e6 + 7;

struct Edge {
	int v, w;
};

int n, q, k;
vector<int> to[N];
vector<Edge> re[N];
vector<int> S, T;
int d[N], dfn[N], c[N], tot;
int px[N][21], siz[N], f[N], mn[N], mx[N];
bool tag[N];

void dfs(int u, int fa, int dp) {
	dfn[u] = ++ tot, c[tot] = fa;
	d[u] = dp, f[u] = fa;
	for (int v : to[u]) {
		if (v == fa) continue;
		dfs(v, u, dp + 1);
	}
}

int cmp(int x, int y) {
	return (dfn[x] < dfn[y] ? x : y);
}

void st_init() {
	for (int i = 1; i <= n; i ++)
		px[i][0] = c[i];
	for (int j = 1; j <= 20; j ++)
		for (int i = 1; i <= n - (1 << j) + 1; i ++)
			px[i][j] = cmp(px[i][j - 1], px[i + (1 << (j - 1))][j - 1]);
}

int getLCA(int u, int v) {
	if (u == v) return u; u = dfn[u], v = dfn[v];
	if (u > v) swap(u, v); u ++;
	int p = __lg(v - u + 1), q = (1 << p);
	int ret = cmp(px[u][p], px[v - q + 1][p]);
//	printf("%d, %d, ret = %d\n", px[dfn[u]][p], px[dfn[v] - q + 1][p], ret);
	return ret;
}

int build(vector<int> &s) {
	sort(s.begin(), s.end(), [&](int x, int y) {
		return dfn[x] < dfn[y];
	});
	vector<int> tmp; tmp.push_back(s[0]);
	for (int i = 1; i < (int) s.size(); i ++) {
		int c = getLCA(s[i - 1], s[i]);
		tmp.push_back(s[i]), tmp.push_back(c);
	}
	sort(tmp.begin(), tmp.end(), [&](int x, int y) {
		return dfn[x] < dfn[y];
	});
	int m = unique(tmp.begin(), tmp.end()) - tmp.begin();
	for (int i = 1; i < m; i ++) {
		int c = getLCA(tmp[i - 1], tmp[i]);
		re[c].push_back({tmp[i], d[tmp[i]] - d[c]});
		re[tmp[i]].push_back({c, d[tmp[i]] - d[c]});
//		printf("%d fa = %d\n", tmp[i], c);
	}
	s = tmp;
	return tmp[0];
}

long long ans1;
int ans2, ans3;

void dfs2(int u, int fa) {
	siz[u] = tag[u];
	mn[u] = (tag[u] ? 0 : 2e9), mx[u] = (tag[u] ? 0 : -2e9);
	for (auto tx : re[u]) {
		int v = tx.v, w = tx.w;
		if (v == fa) continue;
		dfs2(v, u), siz[u] += siz[v];
		ans1 += 1ll * siz[v] * w * (k - siz[v]);
		chkMn(ans2, mn[u] + mn[v] + w);
		chkMx(ans3, mx[u] + mx[v] + w);
		mn[u] = min(mn[u], mn[v] + w);
		mx[u] = max(mx[u], mx[v] + w);
	}
}

void solve() {
	n = read();
	for (int i = 1, u, v; i < n; i ++) {
		u = read(), v = read();
		to[u].push_back(v);
		to[v].push_back(u);
	}
	dfs(1, 0, 0);
	st_init();
	q = read();
	while (q --) {
		k = read(); S.clear();
		for (int i = 1, x; i <= k; i ++)
			x = read(), S.push_back(x), tag[x] = 1;
		T = S; int rt = build(T);
		ans1 = 0, ans2 = 2e9, ans3 = -2e9;
		dfs2(rt, 0);
		cout << ans1 << ' ' << ans2 << ' ' << ans3 << '\n';
		for (int x : T) re[x].clear(), tag[x] = 0;
	}
}

signed main() {
    int t = 1;
    while (t --) solve();
    return 0;
}

CF613D Kingdom and its Cities

题目描述
与此同时,K 王国正在为国王女儿的婚礼做准备。然而,为了不在亲戚面前丢脸,国王必须先完成对王国的改革。由于国王迫不及待地想让女儿出嫁,改革必须尽快完成。

该王国目前由 \(n\) 个城市组成。城市之间通过 \(n-1\) 条双向道路连接,使得任意两个城市之间都可以互相到达。由于国王需要节省开支,所以任意两个城市之间只有一条路径。

改革的目的是什么?国家的重要部门需要分别迁移到不同的城市(我们称这些城市为“重要城市”)。然而,考虑到有蛮族入侵的高风险,必须谨慎进行。国王制定了多个计划,每个计划都描述了一组重要城市,现在他想知道哪个计划最优。

蛮族可以攻占一些非重要城市(重要城市肯定会有足够的保护),被攻占的城市将变得无法通行。具体来说,对于一个计划,其一个有趣的特性是:让所有重要城市都相互隔离(即从任何一个重要城市都无法到达其余重要城市)时,蛮族至少需要攻占多少个城市。

请帮助国王计算每个计划所需攻占的最少城市数。如果蛮族无论怎么攻占,都不能彻底隔离所有重要城市,请输出 \(-1\)

显然,对于虚树上的一条边,在原树上只要被删除一条边,虚树上这条边也会断掉。

因此,设 \(f_{i, 0/1}\) 表示 \(i\) 的子树中,删了一些点之后,\(i\) 所在连通块内是否有关键点。这里,我们称 \(i\) 被删除时是 \(f_{i, 0}\)

考虑 \(f_{u, 0/1}\) 如何转移。

  • 如果 \(u\) 是非关键点,且我们删除了 \(u\),那么就有 \(f_{u, 0} = 1 + \sum \min(f_{v, 0}, f_{v, 1})\)

  • 如果 \(u\) 是关键点,且 \(u\) 没被删除,那么就有 \(f_{u, 1} = \sum \min(f_{v, 0}, f_{v, 1} + 1)\)

  • 如果 \(u\) 是非关键点,且 \(u\) 没被删除,那么就有 \(f_{u, 0} = \sum \min(f_{v, 0}, f_{v, 1} + 1)\),且 \(f_{u, 1} = f_{u, 0} - 1\)\(f_{u, 0}\) 存在一个 \(f_{v, 1} + 1\) 的决策。

注意判断无解即可。

CF613D 代码
/*******************************
| Author:  DE_aemmprty
| Remember:
    * Read the question carefully!!!!!!
    * If you don't make progress on a question within five minutes, it's very likely that you are going in the wrong direction.
    * Don't rush to write code. Take your time.
    * If Div.2D is not made, it indicates that you have been defrauded.
*******************************/
#include <bits/stdc++.h>
using namespace std;

template < typename T, typename...V > void chkMn(T &x, T y) { x = (x < y ? x : y); }
template < typename T, typename...V > void chkMx(T &x, T y) { x = (x < y ? y : x); }

long long read() {
    char c = getchar();
    long long x = 0, p = 1;
    while ((c < '0' || c > '9') && c != '-') c = getchar();
    if (c == '-') p = -1, c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return x * p;
}

const int N = 1e6 + 7;

struct Edge {
	int v, w;
};

int n, q, k;
vector<int> to[N];
vector<Edge> re[N];
vector<int> S, T;
int d[N], dfn[N], c[N], tot;
int px[N][21], siz[N], f[N];
long long dp[N][2];
bool tag[N];

void dfs(int u, int fa, int dp) {
	dfn[u] = ++ tot, c[tot] = fa;
	d[u] = dp, f[u] = fa;
	for (int v : to[u]) {
		if (v == fa) continue;
		dfs(v, u, dp + 1);
	}
}

int cmp(int x, int y) {
	return (dfn[x] < dfn[y] ? x : y);
}

void st_init() {
	for (int i = 1; i <= n; i ++)
		px[i][0] = c[i];
	for (int j = 1; j <= 20; j ++)
		for (int i = 1; i <= n - (1 << j) + 1; i ++)
			px[i][j] = cmp(px[i][j - 1], px[i + (1 << (j - 1))][j - 1]);
}

int getLCA(int u, int v) {
	if (u == v) return u; u = dfn[u], v = dfn[v];
	if (u > v) swap(u, v); u ++;
	int p = __lg(v - u + 1), q = (1 << p);
	int ret = cmp(px[u][p], px[v - q + 1][p]);
//	printf("%d, %d, ret = %d\n", px[dfn[u]][p], px[dfn[v] - q + 1][p], ret);
	return ret;
}

int build(vector<int> &s) {
	sort(s.begin(), s.end(), [&](int x, int y) {
		return dfn[x] < dfn[y];
	});
	vector<int> tmp; tmp.push_back(s[0]);
	for (int i = 1; i < (int) s.size(); i ++) {
		int c = getLCA(s[i - 1], s[i]);
		tmp.push_back(s[i]), tmp.push_back(c);
	}
	sort(tmp.begin(), tmp.end(), [&](int x, int y) {
		return dfn[x] < dfn[y];
	});
	int m = unique(tmp.begin(), tmp.end()) - tmp.begin();
	for (int i = 1; i < m; i ++) {
		int c = getLCA(tmp[i - 1], tmp[i]);
		re[c].push_back({tmp[i], d[tmp[i]] - d[c]});
		re[tmp[i]].push_back({c, d[tmp[i]] - d[c]});
//		printf("%d fa = %d\n", tmp[i], c);
	}
	s = tmp;
	return tmp[0];
}

void dfs2(int u, int fa) {
	siz[u] = tag[u];
	dp[u][0] = dp[u][1] = 2e12;
	long long ret1 = 0, ret2 = 0, ret3 = 2e12;
	for (auto tx : re[u]) {
		int v = tx.v, w = tx.w;
		if (v == fa) continue;
		dfs2(v, u), siz[u] += siz[v];
		ret1 += min(dp[v][0], dp[v][1]);
		ret2 += min(dp[v][0], (w > 1 ? dp[v][1] + 1 : (long long) 2e18));
		chkMn(ret3, min(dp[v][0], dp[v][1]) - min(dp[v][0], (w > 1 ? dp[v][1] + 1 : (long long) 2e18)));
	}
	if (!tag[u]) chkMn(dp[u][0], ret1 + 1);
	if (tag[u]) chkMn(dp[u][1], ret2);
	if (!tag[u]) chkMn(dp[u][0], ret2);
	if (!tag[u]) chkMn(dp[u][1], ret2 + ret3);
}

void solve() {
	n = read();
	for (int i = 1, u, v; i < n; i ++) {
		u = read(), v = read();
		to[u].push_back(v);
		to[v].push_back(u);
	}
	dfs(1, 0, 0);
	st_init();
	q = read();
	while (q --) {
		k = read(); S.clear();
		for (int i = 1, x; i <= k; i ++)
			x = read(), S.push_back(x), tag[x] = 1;
		T = S; int rt = build(T);
		dfs2(rt, 0);
		cout << (min(dp[rt][0], dp[rt][1]) >= 1e12 ? -1 : min(dp[rt][0], dp[rt][1])) << '\n';
		for (int x : T) re[x].clear(), tag[x] = 0;
	}
}

signed main() {
    int t = 1;
    while (t --) solve();
    return 0;
}

P7737 庆典

题目描述
C 国是一个繁荣昌盛的国家,它由 $n$ 座城市和 $m$ 条有向道路组成,城市从 $1$ 到 $n$ 编号。如果从 $x$ 号城市出发,经过若干条道路后能到达 $y$ 号城市,那么我们称 $x$ 号城市可到达 $y$ 号城市,记作 $x\Rightarrow y$。C 国的道路有一个特点:对于三座城市 $x$,$y$,$z$,若 $x\Rightarrow z$ 且 $y\Rightarrow z$,那么有 $x\Rightarrow y$ 或 $y\Rightarrow x$。

再过一个月就是 C 国成立的千年纪念日,所以 C 国的人民正在筹备盛大的游行庆典。目前 C 国得知接下来会有 \(q\) 次游行计划,第 \(i\) 次游行希望从城市 \(s_i\) 出发,经过若干个城市后,在城市 \(t_i\) 结束,且在游行过程中,一个城市可以被经过多次。为了增加游行的乐趣,每次游行还会临时修建出 \(k\)\(0 \le k \le 2\))条有向道路专门供本次游行使用,即其它游行计划不能通过本次游行修建的道路。

现在 C 国想知道,每次游行计划可能会经过多少座城市

注意:临时修建出的道路可以不满足 C 国道路原有的特点

首先容易发现我们只关注连通性。因此,考虑删除一些边,只留下一些边使得点与点之间的可达关系仍然不变。

image

我们关注这个红点。假设这个红点的入度 \(\geq 2\),那随便选两个点,这两个点之间一定有可达关系。如果我们只考虑连通性,显然有一条边可以被去除,这时候红点的入读会减去 \(1\)

通过这种删边操作,我们可以把所有点的入读都变成 \(\geq 1\)。考虑缩点,由于缩点之后是一个 DAG,并且每个点的入读都 \(\geq 1\),因此这一定是一颗树。

所以,我们对原图缩点后,这颗树的可达关系呈现出一棵树的形式。求出这棵树很简单,拓扑排序即可。

那么对于每个询问,将新增了有向边的点和 \(s, t\) 放到一起求出虚树,然后暴力跑出答案即可。

点击查看代码
/*******************************
| Author:  DE_aemmprty
| Remember:
    * Read the question carefully!!!!!!
    * If you don't make progress on a question within five minutes, it's very likely that you are going in the wrong direction.
    * Don't rush to write code. Take your time.
    * If Div.2D is not made, it indicates that you have been defrauded.
*******************************/
#include <bits/stdc++.h>
using namespace std;

template < typename T, typename...V > void chkMn(T &x, T y) { x = (x < y ? x : y); }
template < typename T, typename...V > void chkMx(T &x, T y) { x = (x < y ? y : x); }

long long read() {
    char c = getchar();
    long long x = 0, p = 1;
    while ((c < '0' || c > '9') && c != '-') c = getchar();
    if (c == '-') p = -1, c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return x * p;
}

const int N = 1e6 + 7;

struct Node { int v, id; };
struct Edge { int v, w, id; };
struct Road { int u, v; };

vector<Node> wjc[N];
vector<Edge> re[N * 2], re2[N * 2];
vector<int> to[N * 2];

// Need: graph(vector<Node> wjc[N]) -> graph(vector<int> to[N * 2])
namespace SCC {
    int __n__ = 0, dfn[N], low[N], bel[N], tot, col;
    vector<int> scc[N];
    stack<int> st;
    void Tarjan(int u) {
        low[u] = dfn[u] = ++ tot, st.push(u);
        for (auto shx : wjc[u]) {
            int v = shx.v;
            if (!dfn[v]) {
                Tarjan(v);
                low[u] = min(low[u], low[v]);
            } else if (!bel[v]) {
                low[u] = min(low[u], dfn[v]);
            }
        }
        if (low[u] == dfn[u]) {
            col ++; int x;
            do { x = st.top(); bel[x] = col; scc[col].push_back(x); st.pop(); } while (u != x);
        }
    }

    void Build(int n) {
        for (int i = 1; i <= n; i ++)
            for (auto v : wjc[i])
                if (bel[i] != bel[v.v])
                    to[bel[i]].push_back(bel[v.v]);
    }

    void outputSCC() {
        cout << "==ECC==\n";
        for (int i = 1; i <= col; i ++) {
            for (int x : scc[i])
                cout << x << ' ';
            cout << '\n';
        }
        cout << "==end==\n";
    }

    void __init__() {
        for (int i = 1; i <= __n__; i ++)
            bel[i] = dfn[i] = low[i] = 0;
        for (int i = 1; i <= __n__ + col; i ++) to[i].clear();
        for (int i = 1; i <= col; i ++)
            scc[i].clear();
        tot = col = 0;
        while (!st.empty())
            st.pop();
    }

    void getSCC(int n) {
        __init__(); __n__ = n;
        for (int i = 1; i <= n; i ++)
            if (!dfn[i]) Tarjan(i);
    }
}

namespace topo {
    int __n__, ind[N];
    vector<int> tmp[N];
    void __init__(int n) {
        for (int i = 1; i <= __n__; i ++)
            tmp[i].clear(), ind[i] = 0;
        __n__ = n;
    }

    int topo() {
        for (int i = 1; i <= __n__; i ++)
            for (int v : to[i])
                ind[v] ++;
        vector<int> q;
        for (int i = 1; i <= __n__; i ++)
            if (!ind[i]) q.push_back(i);
        for (int i = 0; i < __n__; i ++) {
            int x = q[i];
            for (int v : to[x]) {
                ind[v] --;
                if (!ind[v]) {
                    tmp[x].push_back(v);
                    q.push_back(v);
                }
            }
        }
        for (int i = 1; i <= __n__; i ++)
            to[i] = tmp[i];
        return q[0];
    }
}

// Need: graph(vector<int> to[N]) -> graph(vector<Edge> re[N])
namespace VT {
    int __n__, px[N][21], d[N], dfn[N], w[N], tot, etot;
    bool imp[N];
    void dfs(int u, int fa, int dp) {
        w[u] = (int) SCC::scc[u].size(); dp += w[u];
        dfn[u] = ++ tot, px[tot][0] = fa, d[u] = dp;
        for (int v : to[u]) {
            if (v == fa) continue;
            dfs(v, u, dp);
        }
    }

    int cmp(int x, int y) {
        return (dfn[x] < dfn[y] ? x : y);
    }

    void st_init() {
        for (int j = 1; j <= 20; j ++)
            for (int i = 1; i <= __n__ - (1 << j) + 1; i ++)
                px[i][j] = cmp(px[i][j - 1], px[i + (1 << (j - 1))][j - 1]);
    }

    int getLCA(int u, int v) {
        if (u == v) return u; u = dfn[u], v = dfn[v];
        if (u > v) swap(u, v); u ++;
        int p = __lg(v - u + 1), q = (1 << p);
        int ret = cmp(px[u][p], px[v - q + 1][p]);
        return ret;
    }

    int Build(vector<int> &s) {
        sort(s.begin(), s.end(), [&](int x, int y) {
            return dfn[x] < dfn[y];
        });
        for (int x : s) imp[x] = 1;
        vector<int> tmp; tmp.push_back(s[0]);
        for (int i = 1; i < (int) s.size(); i ++) {
            int c = getLCA(s[i - 1], s[i]);
            tmp.push_back(s[i]), tmp.push_back(c);
        }
        sort(tmp.begin(), tmp.end(), [&](int x, int y) {
            return dfn[x] < dfn[y];
        });
        int m = unique(tmp.begin(), tmp.end()) - tmp.begin();
        for (int i = 1; i < m; i ++) {
            int c = getLCA(tmp[i - 1], tmp[i]);
            re[c].push_back({tmp[i], d[tmp[i]] - d[c] - w[tmp[i]], ++ etot});
            re2[tmp[i]].push_back({c, d[tmp[i]] - d[c] - w[tmp[i]], etot});
        }
        s = tmp;
        return s[0];
    }

    void clear(vector<int> s) {
        etot = 0;
        for (int i : s) {
            re[i].clear();
            re2[i].clear();
            imp[i] = 0;
        }
    }

    void __init__(int siz, int u) {
        __n__ = siz, etot = tot = 0;
        dfs(u, 0, 1);
        st_init();
    }
}

namespace mian {
    int n, m, q, k;
    vector<int> S, T;
    Road p[7];
    bool vis[N][2], evis[N][2];

    void clear() {
        for (int i = 1; i <= n; i ++)
            wjc[i].clear();
    }

    void dfsS(int u) {
        if (vis[u][0]) return ;
        vis[u][0] = 1;
        for (auto v : re[u]) {
            dfsS(v.v);
            evis[v.id][0] = 1;
        }
    }

    void dfsT(int u, int &res) {
        if (vis[u][1]) return ;
        vis[u][1] = 1; res += vis[u][0] * ((int) SCC::scc[u].size());
        for (auto v : re2[u]) {
            dfsT(v.v, res);
            res += (evis[v.id][1] == 0 && evis[v.id][0] == 1) * v.w;
            evis[v.id][1] = 1;
        }
    }

    void solve() {
        n = read(), m = read(), q = read(), k = read();
        for (int i = 1, u, v; i <= m; i ++) {
            u = read(), v = read();
            wjc[u].push_back({v, i});
        }
        SCC::getSCC(n), SCC::Build(n); int m = SCC::col;
        topo::__init__(m);
        VT::__init__(m, topo::topo());
        while (q --) {
            int s = read(), t = read();
            S.clear(), S.push_back(SCC::bel[s]), S.push_back(SCC::bel[t]);
            for (int i = 1; i <= k; i ++) {
                p[i].u = read(), p[i].v = read();
                S.push_back(SCC::bel[p[i].u]), S.push_back(SCC::bel[p[i].v]);
            }
            VT::Build((T = S));
            for (int i = 1; i <= k; i ++) {
                re[SCC::bel[p[i].u]].push_back({SCC::bel[p[i].v], 0, ++ VT::etot});
                re2[SCC::bel[p[i].v]].push_back({SCC::bel[p[i].u], 0, VT::etot});
            }
            int res = 0;
            dfsS(SCC::bel[s]), dfsT(SCC::bel[t], res);
            cout << res << '\n';
            for (int x : T) vis[x][0] = vis[x][1] = 0;
            for (int i = 1; i <= VT::etot; i ++) evis[i][0] = evis[i][1] = 0;
            VT::clear(T);
        }
        clear();
    }
}

signed main() {
    int t = 1;
    while (t --)
        mian::solve();
    return 0;
}

P4606 战略游戏

题目描述
省选临近,放飞自我的小 Q 无心刷题,于是怂恿小 C 和他一起颓废,玩起了一款战略游戏。

这款战略游戏的地图由 \(n\) 个城市以及 \(m\) 条连接这些城市的双向道路构成,并且从任意一个城市出发总能沿着道路走到任意其他城市。

现在小 C 已经占领了其中至少两个城市,小 Q 可以摧毁一个小 C 没占领的城市,同时摧毁所有连接这个城市的道路。只要在摧毁这个城市之后能够找到某两个小 C 占领的城市 \(u\)\(v\),使得从 \(u\) 出发沿着道路无论如何都不能走到 \(v\),那么小 Q 就能赢下这一局游戏。

小 Q 和小 C 一共进行了 \(q\) 局游戏,每一局游戏会给出小 C 占领的城市集合 \(S\),你需要帮小 Q 数出有多少个城市在他摧毁之后能够让他赢下这一局游戏。

缩点,建出圆方树,在圆方树上跑虚树,计算边权上的割点个数 + 非叶子非关键点的割点个数即可。

点击查看代码
/*******************************
| Author:  DE_aemmprty
| Remember:
    * Read the question carefully!!!!!!
    * If you don't make progress on a question within five minutes, it's very likely that you are going in the wrong direction.
    * Don't rush to write code. Take your time.
    * If Div.2D is not made, it indicates that you have been defrauded.
*******************************/
#include <bits/stdc++.h>
using namespace std;

template < typename T, typename...V > void chkMn(T &x, T y) { x = (x < y ? x : y); }
template < typename T, typename...V > void chkMx(T &x, T y) { x = (x < y ? y : x); }

long long read() {
    char c = getchar();
    long long x = 0, p = 1;
    while ((c < '0' || c > '9') && c != '-') c = getchar();
    if (c == '-') p = -1, c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return x * p;
}

const int N = 1e6 + 7;

struct Node { int v, id; };
struct Edge { int v, w; };

vector<Node> wjc[N];
vector<Edge> re[N * 2];
vector<int> to[N * 2];

// Need: graph(vector<Node> wjc[N]) -> graph(vector<int> to[N * 2])
namespace VCC {
    int __n__ = 0, dfn[N], low[N], cnt[N], tot, col;
    bool cut[N];
    vector<int> vcc[N];
    stack<int> st;
    void Tarjan(int u, int eid) {
        low[u] = dfn[u] = ++ tot, st.push(u);
        int son = 0;
        for (auto shx : wjc[u]) {
            int v = shx.v, id = shx.id;
            if (!dfn[v]) {
                son ++, Tarjan(v, id);
                low[u] = min(low[u], low[v]);
                if (low[v] >= dfn[u]) {
                    col ++; int tmp;
                    do { tmp = st.top(); vcc[col].push_back(tmp); st.pop(); } while (tmp != v);
                    vcc[col].push_back(u);
                }
            } else if (id != eid) {
                low[u] = min(low[u], dfn[v]);
            }
        }
        if (!eid && !son) vcc[++ col].push_back(u);
    }

    void Build(int n) {
        for (int i = 1; i <= col; i ++) {
            ++ n;
            for (int x : vcc[i]) {
                to[x].push_back(n);
                to[n].push_back(x);
                cnt[x] ++;
            }
        }
        for (int i = 1; i <= n; i ++)
            if (cnt[i] > 1) cut[i] = 1;
    }

    void outputVCC() {
        cout << "==VCC==\n";
        for (int i = 1; i <= col; i ++) {
            for (int x : vcc[i])
                cout << x << ' ';
            cout << '\n';
        }
        cout << "==end==\n";
    }

    void __init__() {
        // printf("__n__ = %d\n", __n__);
        for (int i = 1; i <= __n__; i ++)
            cnt[i] = cut[i] = dfn[i] = low[i] = 0;
        for (int i = 1; i <= __n__ + col; i ++) to[i].clear();
        for (int i = 1; i <= col; i ++)
            vcc[i].clear();
        tot = col = 0;
        while (!st.empty())
            st.pop();
    }

    void getVCC(int n) {
        __init__(); __n__ = n;
        for (int i = 1; i <= n; i ++)
            if (!dfn[i]) Tarjan(i, 0);
    }
}

// Need: graph(vector<int> to[N]) -> graph(vector<Edge> re[N])
namespace VT {
    int __n__, px[N][21], d[N], dfn[N], tot;
    bool imp[N];
    void dfs(int u, int fa, int dp) {
        dfn[u] = ++ tot, px[tot][0] = fa, d[u] = dp;
        for (int v : to[u]) {
            if (v == fa) continue;
            dfs(v, u, dp + VCC::cut[v]);
        }
    }

    int cmp(int x, int y) {
        return (dfn[x] < dfn[y] ? x : y);
    }

    void st_init() {
        for (int j = 1; j <= 20; j ++)
            for (int i = 1; i <= __n__ - (1 << j) + 1; i ++)
                px[i][j] = cmp(px[i][j - 1], px[i + (1 << (j - 1))][j - 1]);
    }

    int getLCA(int u, int v) {
        if (u == v) return u; u = dfn[u], v = dfn[v];
        if (u > v) swap(u, v); u ++;
        int p = __lg(v - u + 1), q = (1 << p);
        int ret = cmp(px[u][p], px[v - q + 1][p]);
        return ret;
    }

    int Build(vector<int> &s) {
        sort(s.begin(), s.end(), [&](int x, int y) {
            return dfn[x] < dfn[y];
        });
        for (int x : s) imp[x] = 1;
        vector<int> tmp; tmp.push_back(s[0]);
        for (int i = 1; i < (int) s.size(); i ++) {
            int c = getLCA(s[i - 1], s[i]);
            tmp.push_back(s[i]), tmp.push_back(c);
        }
        sort(tmp.begin(), tmp.end(), [&](int x, int y) {
            return dfn[x] < dfn[y];
        });
        int m = unique(tmp.begin(), tmp.end()) - tmp.begin();
        for (int i = 1; i < m; i ++) {
            int c = getLCA(tmp[i - 1], tmp[i]);
            re[c].push_back({tmp[i], d[tmp[i]] - d[c] - VCC::cut[tmp[i]]});
            re[tmp[i]].push_back({c, d[tmp[i]] - d[c] - VCC::cut[tmp[i]]});
        }
        s = tmp;
        return s[0];
    }

    void clear(vector<int> s) {
        for (int i : s) {
            re[i].clear();
            imp[i] = 0;
        }
    }

    void __init__(int siz, int u) {
        __n__ = siz, tot = 0;
        dfs(u, 0, 1);
        st_init();
    }
}

namespace mian {
    int n, m, q, k;
    vector<int> S, T;
    void clear() {
        for (int i = 1; i <= n; i ++)
            wjc[i].clear();
    }

    void dfs2(int u, int fa, int &ans) {
        bool lf = 1;
        for (Edge wjc : re[u]) {
            int v = wjc.v, w = wjc.w;
            if (v == fa) continue;
            dfs2(v, u, ans);
            ans += w, lf = 0;
        }
        if (!lf) ans += (VCC::cut[u] && !VT::imp[u]);
    }

    void solve() {
        n = read(), m = read();
        for (int i = 1, u, v; i <= m; i ++) {
            u = read(), v = read();
            wjc[u].push_back({v, i});
            wjc[v].push_back({u, i});
        }
        VCC::getVCC(n), VCC::Build(n);
        int m = n + VCC::col;
        VT::__init__(m, 1);
        q = read();
        while (q --) {
            k = read(); S.clear();
            for (int i = 1, x; i <= k; i ++)
                x = read(), S.push_back(x);
            int rt = VT::Build((T = S)), ans = 0;
            dfs2(rt, 0, ans);
            cout << ans << '\n';
            VT::clear(T);
        }
        clear();
    }
}

signed main() {
    int t = read();
    while (t --)
        mian::solve();
    return 0;
}

QOJ9561

题目描述

给定 \(N, V\),求 \(\sum f(T)\)\(P\) 取模后的结果。

对于一棵有 \(N\) 个节点的无根树 \(T\),定义 \(f(T)\) 为包含该树的每个节点最小权值的方案数,令节点的权值为 \(a_i\),定义一个赋权方案,当且仅当所有节点的所有非空连通块 \(S\),都满足:\(mex(\{a_i | i \in S\}) = \min(\{a_i | i \notin S\})\)

此处定义 \(mex(E)\) 为集合 \(E\) 集合内没有出现过的最小非负整数,\(min(E)\)\(E\) 集合内元素的最小值。

特别的,定义 \(mex(\emptyset) = 0, min(\emptyset) = V + 1\)

题目解析

显然 \(0\)\(V\) 的所有权值都要出现。因此当 \(V \geq n\) 时,答案一定是 \(0\)

考虑一个结论,对于 \(0 \leq a_i \leq x\) 的所有 \(i\) 构成的虚树,权值为 \(x + 1\) 的点,要么都在虚树内,要么只有一个点、这个点在虚树外面。

\(f_{i, j, k}\) 表示当前考虑了 \(0 \leq a_x \leq i\) 的点,虚树上有权值的点共 \(j\) 个,没有权值的虚点共 \(k\) 个。注意目前的标号只考虑了 \([1, j]\)

\[\begin{aligned} f_{i, j + b, k - b} \leftarrow & \sum_{a, b}f_{i - 1, j, k} \times A_{n-j}^{a} \times A_{n - j - a}^{b} \times \binom{a + j + k - 2}{a}\times \binom{k}{b}\\ f_{i, j + 1, k} \leftarrow & f_{i - 1, j, k} \times (j + k + 1) \times (n - j)\\ f_{i, j + 1, k + 1} \leftarrow & f_{i - 1, j, k} \times (j + k) \times (n - j) \end{aligned} \]

详细解释:

  • \(f_{i, j + a + b, k - b} \leftarrow \sum_{a, b}f_{i - 1, j, k} \times A_{n-j}^{a} \times A_{n - j - a}^{b} \times \binom{a + j + k - 2}{j + k - 2}\times \binom{k}{b}\)

这种情况下,所有权值为 \(i\) 的点都在虚树内,其中有 \(a\) 个在虚树的边上,\(b\) 个在虚点上。

我们先给这 \(a + b\) 个点分配编号,也就是 \(A_{n-j}^{a} \times A_{n - j - a}^{b}\)。然后,我们要把 \(a\) 个点分到 \(j + k - 1\) 条边上,系数为 \(\binom{a + j + k - 2}{j + k - 2}\)。并且我们要把 \(b\) 个点分到 \(k\) 个虚点上,也就是 \(\binom{k}{b}\)

  • \(f_{i, j + 1, k} \leftarrow f_{i - 1, j, k} \times (j + k + 1) \times (n - j)\)

这时,只有一个权值为 \(i\) 的点,并且这个点挂在原来虚树上的某个点上。我们可以选择 \(j + k\) 个点作为父亲,还可以选择这个点作为根,并且这个点有 \(n - j\) 种编号方式,因此系数为 \((j + k + 1)\times (n - j)\)

  • \(f_{i, j + 1, k + 1} \leftarrow f_{i - 1, j, k} \times (j + k) \times (n - j)\)

这个转移式表示这个权值为 \(i\) 的点挂在虚树的边上,这时会新增一个虚点,并且有 \(j + k\) 条边选择,有 \(n - j\) 种编号方式,因此系数为 \((j + k) \times (n - j)\)


但是上面的转移式是 \(\mathcal{O}(Vn^4)\) 的,无法通过。优化很简单,我们将第一个转移中枚举 \(a, b\) 的部分拆成先枚举 \(b\),再枚举 \(a\),因为将点插入虚点不会影响边的数量。

题目代码

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;

long long read() {
	long long x = 0, k = 1; char c = getchar();
	while (c != '-' && (c < '0' || c > '9')) c = getchar();
	if (c == '-') k = -1, c = getchar();
	while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
	return x * k;
}

const int N = 157;
const int M = 457;
const int T = 450;

long long n, v, mod;
long long f[N][N][N], c[M][M], fac[M];

long long C(long long x, long long y) {
	if (y < 0 || x < 0) return 0;
	return c[x][y];
}

long long A(long long x, long long y) {
	if (y < 0 || x < 0) return 0;
	return C(x, y) * fac[y] % mod;
}

signed main() {
	n = read(), v = read(), mod = read();
	f[0][1][0] = 1, c[0][0] = fac[0] = 1 % mod;
	for (int i = 1; i <= T; i ++) {
		c[i][0] = 1 % mod, fac[i] = fac[i - 1] * i % mod;
		for (int j = 1; j <= i; j ++)
			c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mod;
	}
	if (v >= n) {
		puts("0");
		return 0;
	}
	for (int i = 1; i <= v; i ++) {
		for (int j = n; j >= 1; j --)
			for (int k = 0; k <= j; k ++) {
				if (!f[i - 1][j][k]) continue;
				for (int b = 0; b <= k && b <= n - j; b ++)
					(f[i][j + b][k - b] += f[i - 1][j][k] * A(n - j, b) % mod * C(k, b) % mod) %= mod;
			}
		for (int j = n; j >= 1; j --) {
			for (int k = 0; k <= j; k ++) {
				if (f[i][j][k])
					for (int a = 1; a <= n - j; a ++)
						(f[i][j + a][k] += f[i][j][k] * A(n - j, a) % mod * C(a + j + k - 2, a) % mod) %= mod;
				(f[i][j + 1][k] += f[i - 1][j][k] * (j + k + 1) % mod * (n - j) % mod) %= mod;
				(f[i][j + 1][k + 1] += f[i - 1][j][k] * (j + k) % mod * (n - j) % mod) %= mod;
				f[i][j][k] = (f[i][j][k] - f[i - 1][j][k] + mod) % mod;
			}
		}
	}
	cout << f[v][n][0] << '\n';
	return 0;
}

CF809E

题目描述

厌倦了无聊约会的 Leha 和 Noora 决定玩一个游戏。

Leha 找到了一棵有 \(n\) 个顶点的树,顶点编号为 \(1\)\(n\)。我们提醒你,树是一种无环的无向图。树的每个顶点 \(v\) 上写有一个数字 \(a_{v}\)。碰巧的是,所有顶点上的数字两两不同,且均为 \(1\)\(n\) 的自然数。

游戏的规则如下:Noora 均匀随机地选择树上的一个顶点 \(u\),然后轮到 Leha 行动。Leha 也均匀随机地从剩下的顶点中选择一个顶点 \(v\)\(v \neq u\))。显然,两人选择顶点的方式共有 \(n(n-1)\) 种。

接下来,玩家们计算函数 \(f(u,v)=\varphi(a_{u} \cdot a_{v}) \cdot d(u,v)\) 的值,其中 \(\varphi(x)\) 表示欧拉函数,\(d(x,y)\) 表示树上顶点 \(x\)\(y\) 之间的最短距离。

很快这个游戏就令 Noora 感到无趣,于是 Leha 决定活跃气氛,计算所有选择顶点 \(u\)\(v\) 的方案下函数 \(f\) 的期望值,希望能让女孩惊喜一下。

Leha 请求你帮忙计算这个期望值。假设该期望值可以表示为最简分数 \(\frac{P}{Q}\)。为了更进一步给 Noora 惊喜,他希望你报出 \(P \cdot Q^{-1} \bmod 10^9+7\) 的值。

请帮 Leha 完成这个任务!

题目解析

题目要我们求 \(\dfrac{1}{n(n - 1)}\sum\limits_{i = 1}^{n}\sum\limits_{j = 1}^n \varphi(a_ia_j)\mathrm{dist}(i, j)\)

考虑对 \(\sum\limits_{i = 1}^{n}\sum\limits_{j = 1}^n \varphi(a_ia_j)\mathrm{dist}(i, j)\) 进行反演。先来回顾一下莫比乌斯反演:

\[\begin{aligned} g = f * \mu \Rightarrow g(n) = \sum_{d \mid n} f(d) \end{aligned} \]

先化简式子,设 \(f(n) = \dfrac{n}{\varphi(n)}\)

\[\begin{aligned} & \sum\limits_{i = 1}^{n}\sum\limits_{j = 1}^n \varphi(a_ia_j)\mathrm{dist}(i, j)\\ = & \sum\limits_{i = 1}^{n}\sum\limits_{j = 1}^n \dfrac{\varphi(a_i)\varphi(a_j)\gcd(a_i, a_j)}{\varphi(\gcd(a_i, a_j))}\mathrm{dist}(i, j) \\ = & \sum\limits_{i = 1}^{n}\sum\limits_{j = 1}^n \varphi(a_i)\varphi(a_j)f(\gcd(a_i, a_j))\mathrm{dist}(i, j) \\ \end{aligned} \]

莫反,我们设 \(g(n) = (f * \mu)(n) = \sum\limits_{d\mid n} f(d)\mu(\dfrac{n}{d})\),那么:

\[\begin{aligned} & \sum\limits_{i = 1}^{n}\sum\limits_{j = 1}^n \varphi(a_i)\varphi(a_j)f(\gcd(a_i, a_j))\mathrm{dist}(i, j) \\ = & \sum\limits_{i = 1}^{n}\sum\limits_{j = 1}^n \varphi(a_i)\varphi(a_j)\mathrm{dist}(i, j)\sum_{x \mid a_i, x \mid a_j}g(x) \\ = & \sum_{x = 1}^n g(x) \sum_{x \mid a_i} \sum_{ x\mid a_j} \varphi(a_i)\varphi(a_j)\mathrm{dist}(i, j) \end{aligned} \]

枚举 \(x\),由于 \(a\) 是排列,所以 \(x \mid a_i\) 数量总和量级为 \(\mathcal{O}(n \log n)\) 的。对于后面这个 \(\sum_{x \mid a_i} \sum_{ x\mid a_j} \varphi(a_i)\varphi(a_j)\mathrm{dist}(i, j)\),可以换根 DP 解出。

题目代码

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;

long long read() {
	long long x = 0, k = 1; char c = getchar();
	while (c != '-' && (c < '0' || c > '9')) c = getchar();
	if (c == '-') k = -1, c = getchar();
	while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
	return x * k;
}

#define int long long

const int N = 4e5 + 7;
const long long mod = 1e9 + 7;

struct Edge {
	int v;
	long long w;
	int id;
};

int n;
long long a[N], p[N], wjc[N];
vector<int> to[N];
vector<Edge> re[N];

namespace Eular {
	int d[N], phi[N], miu[N];
	vector<int> p;
	void calc(int n) {
		miu[1] = phi[1] = 1;
		for (int i = 2; i <= n; i ++) {
			if (!d[i]) {
				p.push_back(i);
				d[i] = i;
				phi[i] = i - 1;
				miu[i] = (mod - 1) % mod;
			}
			for (int j = 0; j < (int) p.size(); j ++) {
				if (p[j] * i > n || p[j] > d[i]) break;
				d[i * p[j]] = p[j];
				if (i % p[j] == 0) {
					phi[i * p[j]] = phi[i] * p[j] % mod;
					miu[i * p[j]] = 0;
				} else {
					phi[i * p[j]] = phi[i] * (p[j] - 1) % mod;
					miu[i * p[j]] = (mod - miu[i]) % mod;
				}
			}
		}
	}
}

// Need: graph(vector<int> to[N]) AND weight(int w[N]) -> graph(vector<Edge> re[N])
namespace VT {
    int __n__, px[N][21], d[N], dfn[N], w[N], tot, etot;
    bool imp[N];
    void dfs(int u, int fa, int dp) {
        w[u] = 1, dp += w[u];
        dfn[u] = ++ tot, px[tot][0] = fa, d[u] = dp;
        for (int v : to[u]) {
            if (v == fa) continue;
            dfs(v, u, dp);
        }
    }

    int cmp(int x, int y) {
        return (dfn[x] < dfn[y] ? x : y);
    }

    void st_init() {
        for (int j = 1; j <= 20; j ++)
            for (int i = 1; i <= __n__ - (1 << j) + 1; i ++)
                px[i][j] = cmp(px[i][j - 1], px[i + (1 << (j - 1))][j - 1]);
    }

    int getLCA(int u, int v) {
        if (u == v) return u; u = dfn[u], v = dfn[v];
        if (u > v) swap(u, v); u ++;
        int p = __lg(v - u + 1), q = (1 << p);
        int ret = cmp(px[u][p], px[v - q + 1][p]);
        return ret;
    }

    int Build(vector<int> &s) {
        sort(s.begin(), s.end(), [&](int x, int y) {
            return dfn[x] < dfn[y];
        });
        for (int x : s) imp[x] = 1;
        vector<int> tmp; tmp.push_back(s[0]);
        for (int i = 1; i < (int) s.size(); i ++) {
            int c = getLCA(s[i - 1], s[i]);
            tmp.push_back(s[i]), tmp.push_back(c);
        }
        sort(tmp.begin(), tmp.end(), [&](int x, int y) {
            return dfn[x] < dfn[y];
        });
        int m = unique(tmp.begin(), tmp.end()) - tmp.begin();
        s.clear(); s.push_back(tmp[0]);
        for (int i = 1; i < m; i ++) {
        	s.push_back(tmp[i]);
            int c = getLCA(tmp[i - 1], tmp[i]);
            re[c].push_back({tmp[i], d[tmp[i]] - d[c], ++ etot});
        }
        return s[0];
    }

    void clear(vector<int> s) {
        etot = 0;
        for (int i : s) {
            re[i].clear();
            imp[i] = 0;
        }
    }

    void __init__(int siz, int u) {
        __n__ = siz, etot = tot = 0;
        dfs(u, 0, 1);
        st_init();
    }
}

long long ksm(long long x, long long y = mod - 2) {
	long long res = 1;
	for (; y; y >>= 1, (x *= x) %= mod)
		if (y & 1) (res *= x) %= mod;
	return res;
}

long long dp[N], S[N], ret, res, ans;

void dfs(int u, int fa, long long dis) {
	if (VT::imp[u]) (ret += dis * Eular::phi[a[u]] % mod) %= mod;
	S[u] = VT::imp[u] * Eular::phi[a[u]];
	for (auto v : re[u]) {
		if (v.v == fa) continue;
		dfs(v.v, u, dis + v.w);
		(S[u] += S[v.v]) %= mod;
	}
}

void dfs2(int u, int fa, int rt) {
	if (VT::imp[u]) (res += Eular::phi[a[u]] * dp[u] % mod) %= mod;
	for (auto v : re[u]) {
		if (v.v == fa) continue;
		dp[v.v] = (dp[u] + (S[rt] - S[v.v] * 2 + mod + mod) % mod * v.w % mod) % mod;
		dfs2(v.v, u, rt);
	}
}

signed main() {
	n = read();
	for (int i = 1; i <= n; i ++)
		a[i] = read(), p[a[i]] = i;
	for (int i = 1, u, v; i < n; i ++) {
		u = read(), v = read();
		to[u].push_back(v);
		to[v].push_back(u);
	}
	Eular::calc(200000);
	VT::__init__(n, 1);
	for (int x = 1; x <= n; x ++)
		for (int d = x; d <= n; d += x) {
			(wjc[d] += x * Eular::miu[d / x] % mod * ksm(Eular::phi[x]) % mod) %= mod;
		}
	for (int d = 1; d <= n; d ++) {
		vector<int> s, t;
		for (int i = d; i <= n; i += d)
			s.push_back(p[i]);
		int rt = VT::Build(t = s);
		ret = 0, res = 0;
		dfs(rt, 0, 0); dp[rt] = ret; dfs2(rt, 0, rt);
		(ans += wjc[d] * res % mod) %= mod;
		VT::clear(t);
	}
	cout << ans * ksm(n * (n - 1) % mod) % mod << '\n';
	return 0;
}

P14473

翻涌 至喷涌

谁能懂 少年汹涌

题目描述

希寇给了你长为 \(n\) 的序列 \(a\) 和数组 \(w\)

对于常数 \(L\),定义 \(f(x)\) 为如下操作构成的集合:

  1. \(x\) 加入集合。
  2. \(x \leftarrow x + w_{\operatorname{popcount}(x)}\),其中 \(\operatorname{popcount}(x)\) 表示 \(x\) 二进制下 1 的个数,若 \(x > L\) 结束操作,否则回到第一步。

希寇想知道 \(\bigcup_{i=1}^{n} f(a_i)\) 的大小,你能帮帮他吗?

posted @ 2025-12-13 10:28  DE_aemmprty  阅读(31)  评论(1)    收藏  举报