• Codeforces 1338E: JYPnation


    题目传送门:CF1338E

    题意简述

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

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

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

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

    • (3 le n le 8000)

    题解

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


    一些前置知识和定义

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

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

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


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

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

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

    • 引理 (oldsymbol{1.5})除了 (S_1) 外,之后所有强连通分量都含 (1) 个点。
    • 证明 (oldsymbol{1.5})考虑 (S_1) 中的任意一个点 (x),显然 (mathrm{in}(x)) 中包含所有的 (S_2 sim S_k)
    • 结合引理 (oldsymbol{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) 仍然非空)(强连通图)。

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

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

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

    据此我们可以画出图形:

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

    据此我们可以得到:

    • 引理 (oldsymbol{4})(R) 中无环,(S) 中无环。
    • 证明 (oldsymbol{4})因为 (R subseteq mathrm{in}(v)) 所以显然无环,对于 (R) 中任意一点 (b)(S subseteq mathrm{in}(b)) 也无环。
    • 引理 (oldsymbol{5})(P) 中无环,(Q) 中无环。
    • 证明 (oldsymbol{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|}) 的空前缀(引理 (oldsymbol{6mathrm{a}'}))。

    所以我们有:

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

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

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

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

    • 引理 (oldsymbol{7})对于 (1 le i1 < i2 < |P|),有 (|mathrm{in}Q(P_{i1})| le |mathrm{in}Q(P_{i2})|),类似的命题对 (|mathrm{in}P(Q_j)|) 也成立。
    • 证明 (oldsymbol{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} o u),也就是说 (P_{i2}) 属于 (mathrm{in}P(u))(P_{i1}) 不属于,这与引理 (oldsymbol{6mathrm{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} o z o 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} o x o Q_1 o P_{i2})
      • 否则必然有 (i2 le mathrm{in}P(Q_1) < |P|),路径为 (P_{i1} o Q_1 o P_{|P|} o 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} o z o Q_{j2})
    3. 如果 (j1 < j2)(|mathrm{in}P(Q_{j1})| = |mathrm{in}P(Q_{j2})|),则 (mathrm{dis}(Q_{j1}, Q_{j2}) = 3)
      • 路径为 (Q_{j1} o P_{|P|} o x o 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 o x o q)
    2. 如果存在 (p o q),则 (mathrm{dis}(p, q) = 1)(mathrm{dis}(q, p) = 2),路径为 (q o P_{|P|} o 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
    ", 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
    ", Ans);
    	return 0;
    }
    
  • 相关阅读:
    2.App Components-Activities/Loadres
    2.App Components-Activities/Fragments
    2.App Components-Activities
    2.App Components-Intents and Intent Filters
    1.Indroduction
    Fragment
    用继承和组合方式定制控件
    复杂布局实现
    Android源代码下载与跟踪
    电梯调度算法模拟
  • 原文地址:https://www.cnblogs.com/PinkRabbit/p/CF1338E.html
Copyright © 2020-2023  润新知