Codeforces 1338E: JYPnation

题目传送门:CF1338E

题意简述

给定一张 \(n\) 个点的竞赛图,特别地,满足图中不存在这样的四个点 \(a, b, c, d\)

  • 其中 \(a, b, c\) 三个点形成三元环,即 \(a \to b \to c \to a\),且它们都向 \(d\) 连边,即 \((a, b, c) \to d\)

你需要计算每对点之间的距离之和,即 \(\displaystyle \sum_{\substack{1 \le i, j \le n \\ i \ne j}} \mathrm{dis}(i, j)\)

其中 \(\mathrm{dis}(x, y)\) 定义为 \(x\)\(y\) 需要经过的最少边数,如果 \(x\) 无法到达 \(y\) 则规定 \(\mathrm{dis}(x, y) = 614 \times n\)

  • \(3 \le n \le 8000\)

题解

这是一道数竞题,我负责翻译官方题解并详细展开并配图。


一些前置知识和定义

众所周知,一张竞赛图,在强连通分量(SCC)缩点后,会形成一条链状 DAG。

同时,如果若干个点的导出子图中包含一个环的话,那么其中也必然包含一个三元环(用归纳法)。

\(\mathrm{in}(x) = \{ u \mid u \to x \}\),即所有能够到达 \(x\) 的点的集合,记 \(\deg(x) = |\mathrm{in}(x)|\),即 \(x\) 的入度。


  • 引理 \(\boldsymbol{1}\)对任意一点 \(x\)\(\mathrm{in}(x)\) 中没有环,更进一步地 \([\{x\} \cup \mathrm{in}(x)]\) 中没有环。
  • 证明 \(\boldsymbol{1}\)反证法,如果 \(\mathrm{in}(x)\) 中有环,那么必然有一个三元环都向 \(x\) 连边,与条件冲突。

考虑原图缩点后形成的链状 DAG,令其中点集依次为 \(S_1, S_2, \ldots , S_k\)

\(S_i\) 中的所有点向 \(S_1 \sim S_{i - 1}\) 中的所有点连边,且每个 \(S_i\) 都是一个强连通分量。

  • 引理 \(\boldsymbol{1.5}\)除了 \(S_1\) 外,之后所有强连通分量都含 \(1\) 个点。
  • 证明 \(\boldsymbol{1.5}\)考虑 \(S_1\) 中的任意一个点 \(x\),显然 \(\mathrm{in}(x)\) 中包含所有的 \(S_2 \sim S_k\)
  • 结合引理 \(\boldsymbol{1}\),也就是说 \(S_2 \sim S_k\) 中没有环,而没有环的强连通分量必然只有 \(1\) 个点。

在算法实现中,我们每次(不断寻找)删掉一个无入度的点(即 \(S_k\) 中的唯一一个点)及其出边,直到这样的点不存在为止,剩下的点就都是 \(S_1\) 中的点。

可以很方便地计算这些「被删除的点」对答案的贡献,被删点可以 \(1\) 步到达还未被删除的点,反之则无法到达。


从现在开始我们只考虑最后的一个 \(S_1\) 的情况,也就是说假设原图是一张强连通图。

\(x\)\(\deg\) 最大的点,如有多个任取一个。

\(P = \{x\} \cup \mathrm{in}(x)\),以及 \(Q = V \setminus P\)(其中 \(V\) 为所有点的集合)。

显然 \(\deg(x) = |P| - 1\),且 \(P, Q\) 均非空(且 \(P\) 去掉 \(x\) 仍然非空)(强连通图)。

  • 引理 \(\boldsymbol{2}\)存在两点 \(v, u\),其中 \(v \in P, u \in Q\),且存在边 \(v \leftarrow u\)
  • 证明 \(\boldsymbol{2}\)反证法,假设不存在这样的边,也就是说 \(P\) 中所有点均向 \(Q\) 中所有点连边。
  • 这是不可能的,因为如果是这样则 \(Q\) 中的每个点之 \(\deg\) 至少为 \(|P|\),与 \(\deg(x) = |P| - 1\) 且最大矛盾。

我们取满足引理 \(\boldsymbol{2}\) 中条件的一点 \(v\),令 \(R = [\mathrm{in}(v) \cap Q]\),以及 \(S = Q \setminus R\)

特别地,\(v\) 不可能等于 \(x\),因为 \(Q\) 中所有点都不可能向 \(x\) 连边,是 \(x\)\(Q\) 中所有点连边。

据此我们可以画出图形:

  • 引理 \(\boldsymbol{3}\)对于任意 \(b \in R\)\(a \in S\),必然存在 \(b \leftarrow a\),也就是说 \(S\) 中所有点向 \(R\) 中所有点连边。
  • 证明 \(\boldsymbol{3}\)反证法,假设存在一对 \(b, a\) 违反此结论,也就是说 \(b \to a\)
  • 考虑 \(x, v, a, b\) 这四个点,\(b \to v \to x \to b\) 是三元环,又有 \((x, v, b) \to a\),与初始条件冲突。

据此我们可以得到:

  • 引理 \(\boldsymbol{4}\)\(R\) 中无环,\(S\) 中无环。
  • 证明 \(\boldsymbol{4}\)因为 \(R \subseteq \mathrm{in}(v)\) 所以显然无环,对于 \(R\) 中任意一点 \(b\)\(S \subseteq \mathrm{in}(b)\) 也无环。
  • 引理 \(\boldsymbol{5}\)\(P\) 中无环,\(Q\) 中无环。
  • 证明 \(\boldsymbol{5}\)因为 \(P = \{x\} \cup \mathrm{in}(x)\) 所以显然无环,\(R, S\) 无环且 \(R\)\(S\) 之间无环,所以 \(Q\) 也无环。

这意味着我们将图分成了两部分 \(P\)\(Q\),且每部分都无环。


既然 \(P, Q\) 均有序,我们为 \(P, Q\) 中的每个点进行重编号。

\(P = \{P_1, P_2, \ldots , P_{|P|}\}\) 且满足对于所有 \(1 \le i1 < i2 \le |P|\)\(P_{i1} \leftarrow P_{i2}\)。并且有 \(x = P_1\)

\(Q\) 也进行同样的编号。

\(\mathrm{in}Q(P_i) = \mathrm{in}(P_i) \cap Q\),即 \(P_i\)\(Q\) 中的入点集合。
\(\mathrm{in}P(Q_j) = \mathrm{in}(Q_j) \cap P\),即 \(Q_j\)\(P\) 中的入点集合。

观察之前的图,可以发现 \(\mathrm{in}Q(v)\),也就是 \(R\),是 \(Q_{1 \sim |Q|}\) 的一个前缀。

实际上对于 \(P\) 中的任意一点 \(v\) 都成立,除非 \(\mathrm{in}Q(v) = \varnothing\),此时等同于 \(Q_{1 \sim |Q|}\) 的空前缀(引理 \(\boldsymbol{6\mathrm{a}'}\))。

所以我们有:

  • 引理 \(\boldsymbol{6\mathrm{a}}\)如果 \(|\mathrm{in}Q(P_{i1})| = |\mathrm{in}Q(P_{i2})|\),那么 \(\mathrm{in}Q(P_{i1}) = \mathrm{in}Q(P_{i2})\)
  • 引理 \(\boldsymbol{6\mathrm{a}'}\)任意一个 \(\mathrm{in}Q(P_i)\) 都是 \(Q_{1 \sim |Q|}\) 的一个前缀或空前缀,前文已经证明。
  • 证明 \(\boldsymbol{6\mathrm{a}}\)由前缀的唯一性可得。

事实上,对于 \(Q\) 中的点这个性质也对称地成立:

  • 引理 \(\boldsymbol{6\mathrm{b}}\)如果 \(|\mathrm{in}P(Q_{j1})| = |\mathrm{in}P(Q_{j2})|\),那么 \(\mathrm{in}P(Q_{j1}) = \mathrm{in}P(Q_{j2})\)
  • 引理 \(\boldsymbol{6\mathrm{b}'}\)类似地,只要证明 \(\mathrm{in}P(Q_j)\)\(P_{1 \sim |P|}\) 的一个前缀或空前缀(实际上不可能是空前缀)即可。
  • 证明 \(\boldsymbol{6\mathrm{b}'}\)反证法,假设存在一点 \(c = Q_j\) 满足 \(\mathrm{in}P(c)\) 不是 \(P_{1 \sim |P|}\) 的一个前缀。
  • 也就是说存在 \(d = P_{i1}, e = P_{i2}\)(其中 \(i1 < i2\))满足 \(d \notin \mathrm{in}P(c), e \in \mathrm{in}P(c)\)
  • 也就是说 \(e \to c \to d\),同时还有 \(d \leftarrow e\) 因为 \(i1 < i2\)(如有需要请读者自行画图)。
  • 如果 \(\mathrm{in}Q(e) \ne \varnothing\),我们考虑任意一个 \(f \in \mathrm{in}Q(e)\),从而有 \(f \to e\)
    • 显然 \(c \notin \mathrm{in}Q(e)\),所以如果 \(f = Q_{j0}\),则有 \(j0 < j\),从而有 \(f \leftarrow c\)
    • 由于 \(j0 < j\)\(c \to d\),则也有 \(f \to d\)
    • 考虑 \(f \to e \to c \to f\) 是一个三元环,且 \((e, c, f) \to d\),与初始条件冲突。
  • 否则如果 \(\mathrm{in}Q(e) = \varnothing\),那么 \(e\) 必然不是 \(P_{|P|}\),因为如果是则 \(e\) 不与其他点成强连通分量。
    • 考虑 \(f = Q_1\) 以及 \(g = P_{|P|}\),显然有 \(g \to (e, d)\)\(f \to d\)(如有需要请读者自行画图)。
    • 由于 \(\mathrm{in}Q(e) = \varnothing\),则必然有 \(e \to f\)
    • 考虑 \(\mathrm{in}Q(g)\) 必然不能为空,如果为空则 \(g = P_{|P|}\) 不与其他点成强连通分量。
    • 所以因为 \(|\mathrm{in}Q(g)| \ge 1\),有 \(f \to g\)
    • 考虑 \(g \to e \to f \to g\) 是一个三元环,且 \((e, f, g) \to d\),与初始条件冲突。
  • 综上所述,\(\mathrm{in}P(Q_j)\) 必然是 \(P_{1 \sim |P|}\) 的一个前缀或空前缀,引理 \(\boldsymbol{6\mathrm{b}'}\) 得证。
  • 又由前缀的唯一性可得引理 \(\boldsymbol{6\mathrm{b}}\)

我们还能得到 \(|\mathrm{in}Q(P_i)|\) 以及 \(|\mathrm{in}P(Q_j)|\) 的有序性:

  • 引理 \(\boldsymbol{7}\)对于 \(1 \le i1 < i2 < |P|\),有 \(|\mathrm{in}Q(P_{i1})| \le |\mathrm{in}Q(P_{i2})|\),类似的命题对 \(|\mathrm{in}P(Q_j)|\) 也成立。
  • 证明 \(\boldsymbol{7}\)反证法,假设 \(|\mathrm{in}Q(P_{i1})| > |\mathrm{in}Q(P_{i2})|\),据此有 \(\mathrm{in}Q(P_{i2}) \subsetneq \mathrm{in}Q(P_{i1})\)
  • 取出属于 \(\mathrm{in}Q(P_{i1}) \setminus \mathrm{in}Q(P_{i2})\) 的一点 \(u\)
  • \(P_{i1} \leftarrow u\)\(P_{i2} \to u\),也就是说 \(P_{i2}\) 属于 \(\mathrm{in}P(u)\)\(P_{i1}\) 不属于,这与引理 \(\boldsymbol{6\mathrm{b}'}\) 冲突。
  • 所以必然有 \(|\mathrm{in}Q(P_{i1})| \le |\mathrm{in}Q(P_{i2})|\)
  • 同理可得类似的命题对 \(|\mathrm{in}P(Q_j)|\) 也成立。

注意所有 \(Q\) 中的点都有向 \(P_{|P|}\) 的边,否则意味着某点的度数至少为 \(|P|\),与 \(\deg(x) = |P| - 1\) 且最大矛盾。

最后我们考虑 \(P, Q\) 中的点的 \(\mathrm{dis}\) 关系。

对于 \(P\) 有:

  1. 如果 \(i1 < i2\),则 \(\mathrm{dis}(P_{i2}, P_{i1}) = 1\)
  2. 如果 \(i1 < i2\)\(|\mathrm{in}Q(P_{i1})| < |\mathrm{in}Q(P_{i2})|\),则 \(\mathrm{dis}(P_{i1}, P_{i2}) = 2\)
    • \(z\)\(\mathrm{in}Q(P_{i2}) \setminus \mathrm{in}Q(P_{i1})\) 中任意一点,路径为 \(P_{i1} \to z \to P_{i2}\)
  3. 如果 \(i1 < i2\)\(|\mathrm{in}Q(P_{i1})| = |\mathrm{in}Q(P_{i2})|\),则 \(\mathrm{dis}(P_{i1}, P_{i2}) = 3\)
    • 如果 \(\mathrm{in}P(Q_1) < i1\),则路径为 \(P_{i1} \to x \to Q_1 \to P_{i2}\)
    • 否则必然有 \(i2 \le \mathrm{in}P(Q_1) < |P|\),路径为 \(P_{i1} \to Q_1 \to P_{|P|} \to P_{i2}\)

对于 \(Q\) 有:

  1. 如果 \(j1 < j2\),则 \(\mathrm{dis}(Q_{j2}, Q_{j1}) = 1\)
  2. 如果 \(j1 < j2\)\(|\mathrm{in}P(Q_{j1})| < |\mathrm{in}P(Q_{j2})|\),则 \(\mathrm{dis}(Q_{j1}, Q_{j2}) = 2\)
    • \(z\)\(\mathrm{in}P(Q_{j2}) \setminus \mathrm{in}P(Q_{j1})\) 中任意一点,路径为 \(Q_{j1} \to z \to Q_{j2}\)
  3. 如果 \(j1 < j2\)\(|\mathrm{in}P(Q_{j1})| = |\mathrm{in}P(Q_{j2})|\),则 \(\mathrm{dis}(Q_{j1}, Q_{j2}) = 3\)
    • 路径为 \(Q_{j1} \to P_{|P|} \to x \to Q_{j2}\)

对于 \(P, Q\) 之间的点对 \(p \in P, q \in Q\) 有:

  1. 如果存在 \(p \leftarrow q\),则 \(\mathrm{dis}(q, p) = 1\)\(\mathrm{dis}(p, q) = 2\),路径为 \(p \to x \to q\)
  2. 如果存在 \(p \to q\),则 \(\mathrm{dis}(p, q) = 1\)\(\mathrm{dis}(q, p) = 2\),路径为 \(q \to P_{|P|} \to p\)

综上所述:

  1. 对于所有 \(1 \le i1 < i2 \le |P|\),这对点对答案贡献 \(3 + [|\mathrm{in}Q(P_{i1})| = |\mathrm{in}Q(P_{i2})|]\)
  2. 对于所有 \(1 \le j1 < j2 \le |Q|\),这对点对答案贡献 \(3 + [|\mathrm{in}P(Q_{j1})| = |\mathrm{in}P(Q_{j2})|]\)
  3. 对于所有 \(p \in P, q \in Q\),这对点对答案贡献 \(3\)

在算法实现中,我们可以直接处理出每个点的 \(\mathrm{in}Q\)\(\mathrm{in}P\),然后使用上述结论统计答案。

下面是代码,时间复杂度为 \(\mathcal O (n^2)\)

#include <cstdio>
#include <cctype>
#include <algorithm>
#include <vector>

typedef long long LL;
const int MN = 8005;

char s[MN];
int N, deg[MN], bel[MN], num[MN];
std::vector<bool> A[MN];
int que[MN], lb, rb;
LL Ans;

int main() {
	scanf("%d", &N);
	for (int i = 1; i <= N; ++i) {
		scanf("%s", s + 1);
		A[i].resize(N + 1);
		for (int j = 1; j <= N / 4; ++j) {
			int x = isdigit(s[j]) ? s[j] - '0' : 10 + (s[j] - 'A');
			for (int k = 0; k < 4; ++k)
				A[i][4 * j - k] = x >> k & 1;
		}
	}
	for (int i = 1; i <= N; ++i)
		for (int j = 1; j <= N; ++j) if (A[i][j])
			++deg[j];
	int C = N;
	lb = 1, rb = 0;
	for (int i = 1; i <= N; ++i) if (!deg[i]) que[++rb] = i;
	while (lb <= rb) {
		int u = que[lb++];
		bel[u] = 3;
		Ans += (614ll * N + 1) * --C;
		for (int i = 1; i <= N; ++i) if (A[u][i])
			if (!--deg[i]) que[++rb] = i;
	}
	if (!C) return printf("%lld\n", Ans), 0;
	int x = std::max_element(deg + 1, deg + N + 1) - deg;
	for (int i = 1; i <= N; ++i) if (bel[i] != 3)
		bel[i] = i == x || A[i][x] ? 1 : 2;
	for (int i = 1; i <= N; ++i) if (bel[i] != 3)
		for (int j = 1; j <= N; ++j) if (i != j && bel[j] != 3)
			if (bel[i] != bel[j] && A[i][j]) ++num[j];
	for (int i = 1; i < N; ++i) if (bel[i] != 3)
		for (int j = i + 1; j <= N; ++j) if (bel[j] != 3)
			Ans += 3 + (bel[i] == bel[j] && num[i] == num[j]);
	printf("%lld\n", Ans);
	return 0;
}
posted @ 2020-05-12 04:44  粉兔  阅读(587)  评论(0编辑  收藏  举报