专栏文章

题解:AT_arc111_d [ARC111D] Orientation

AT_arc111_d题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@min8dk9l
此快照首次捕获于
2025/12/01 22:14
3 个月前
此快照最后确认于
2025/12/01 22:14
3 个月前
查看原文

简要题意

给定一张 nn 个点 mm 条边的简单无向图与一个正整数序列 c1,c2,,cnc_1,c_2, \dots,c_n
目标是将每条无向边 (ui,vi)(u_i, v_i) 定向成 uiviu_i \to v_iviuiv_i \to u_i,得到一张有向图,使得对于每个点 ii,从 ii 出发可达的点(包括 ii 自己)恰好有 cic_i 个,报告任意一种合法的定向方案。
  • 1n1001 \le n \le 1000mn(n1)20 \le m \le \frac{n(n-1)}{2}
  • 1ui,vin1 \le u_i, v_i \le n
  • 1cin1 \le c_i \le n
  • 保证输入一定存在至少一种合法定向

R(i)R(i) 为点 ii 可达的点集(包括 ii 自己),题目给出的 cic_i 即是对 R(i)|R(i)| 的要求。

关键观察

假设有一条有向边 uvu \to v,一定有 R(v)R(u)R(v) \subseteq R(u)

证明: xR(v)\forall \ x \in R(v),都可以走 uvxu \to v \leadsto x,所以一定有 xR(u)x \in R(u),符合 R(v)R(u)R(v) \subseteq R(u) 的定义。
有了这个观察,可以立刻得到:
  1. R(v)R(u)|R(v)| \leq |R(u)|,即 cvcuc_v \leq c_u 是必要的。
  2. 如果 R(v)=R(u)|R(v)| = |R(u)|,即 cv=cuc_v = c_u 是必要的,又因为 R(v)R(u)R(v) \subseteq R(u),只能是 R(v)=R(u)R(v) = R(u)

设有一条无向边 {u,v}\{u, v\}

cu<cvc_u < c_vuvu \gets v 是必要的

反证,设 uvu \to v,根据 1.,要求 cvcuc_v \leq c_u,与 cu<cvc_u < c_v 矛盾,因此 uvu \gets v 是必要的。

cu>cvc_u > c_vuvu \to v 是必要的

证明同上。

cu=cvc_u = c_v:强连通是必要的

假设是 uvu \to v,根据 2.,要求 R(v)=R(u)R(v) = R(u)。由于一定有 uR(u)u \in R(u),因此 uR(v)u \in R(v),即 uuvv 强连通。
假设 uvu \gets v 会获得一样的结果。因此,只能得到 uuvv 强连通是必要的。

着手解决

当问题似乎无法变得更简单时,或许可以试着开始着手解决了。
先总结一下目前拥有的观察:
  • 对于 cu<cvc_u < c_vuvu \gets v 是必要的
  • 对于 cu>cvc_u > c_vuvu \to v 是必要的
直接按必要性定向,这部分必要性自然满足。
  • 对于 cui=cvic_{u_i} = c_{v_i}uiu_iviv_i 强连通是必要的
注意到强连通是有传递性的,即,若 uuvv 强连通,vvww 强连通,则 uuww 强连通。
因此考虑构建一张子图,所有满足 cui=cvic_{u_i} = c_{v_i}(ui,vi)(u_i, v_i) 为元素组成的集合为该子图的边集,每条边的两个顶点分别为元素组成的集合为该子图的点集。
还需满足的必要条件:为这张子图的每个极大连通子图(连通块)的每条边定向,使得每个极大连通子图都是强连通图。

为一张无向图的每条边定向,使得其强连通

小名鼎鼎的 Robbins 定理指出:存在方案的充要条件为:此图无桥(割边)。
In graph theory, Robbins' theorem, named after Herbert Robbins (1939), states that the graphs that have strong orientations are exactly the 2-edge-connected graphs. That is, it is possible to choose a direction for each edge of an undirected graph G, turning it into a directed graph that has a path from every vertex to every other vertex, if and only if G is connected and has no bridge.
由于题目保证有解,且此为必要条件,因此一定存在方案,即子图的每个极大连通子图一定不存在桥。
定理附上的构造,这是我们所关心的:
A strong orientation of a given bridgeless undirected graph may be found in linear time by performing a depth-first search of the graph, orienting all edges in the depth-first search tree away from the tree root, and orienting all the remaining edges (which must necessarily connect an ancestor and a descendant in the depth-first search tree) from the descendant to the ancestor.
  1. 任取此图的任意一棵 DFS 生成树
由于是无向图的 DFS 生成树,因此只存在树边与返祖边。
  1. 将每条树边按父亲指向儿子定向
  2. 将每条返祖边按后代指向祖先定向
流程结束,若此图无桥,此图一定强连通。

比具体的、严谨的证明更易懂的,本质的观察是:

由求桥时的 tarjan 算法:
图无桥     \iff 对于图的 DFS 树上每个非根结点 uu,都有 lowu<dfnu\text{low}_u < \text{dfn}_u
由求强连通分量时的 tarjan 算法:
对于图的 DFS 树上每个非根结点 uu,都有 lowu<dfnu\text{low}_u < \text{dfn}_u     \iff 没有任何一棵非根结点 uu 的子树单独形成一个强连通分量
又因为对于根 rr 一定有 lowr=dfnr\text{low}_r = \text{dfn}_r,因此此图只有一个强连通分量,即整张图,即此图强连通。

充分性呢?

有没有发现,目前为止都只研究了必要性,即在题目给定的信息下,有哪些条件是必须满足的,若不满足,一定与给定的信息矛盾。
但充分性该如何保证?即,还需要满足哪些条件,才能符合题目的信息?即满足 i{1,2,,n}, R(i)=ci\forall i \in \{1,2,\dots,n\}, \ |R(i)| = c_i
实际上,已经充分了。因为在满足这些必要条件的前提下,所有合法的定向,计算出的 R(i)|R(i)| 序列始终唯一,而题目保证一定有解,因此必要也一定是充分的。
非严谨证明,大概的观察:
在必要条件的前提下,无论怎么定向:
  • 根据关键观察 2.,同一强连通分量内的 R(i)R(i) 始终相等
  • 将每个强连通分量缩点之后的图称为新图,新图始终唯一
由此每个点的可达点集 R(i)R(i) 序列始终唯一,因此 R(i)|R(i)| 序列始终唯一,证毕。
至此,思路部分完结散花!

实现

伪代码

  • 读入 n,mn, m
  • 读入 (ui,vi)(u_i, v_i)
  • 读入 cic_i
  • 对于每条边 (u,v)(u, v):
    • 如果 cu<cvc_u < c_v,将此条边定向标记为 uvu \gets v
    • 如果 cu>cvc_u > c_v,将此条边定向标记为 uvu \to v
    • 如果 cu=cvc_u = c_v,将 u,vu, v 分别加入子图的点集,将 (u,v)(u, v) 加入子图的边集
  • 对于子图的每个连通块
    • 任取该连通块的一棵 DFS 生成树
    • 将每条树边的定向标记为父亲指向儿子
    • 将每条返祖边的定向标记为后代指向祖先
  • 报告每条边的定向

真代码,C++14 标准

CPP
#include <iostream>
#include <vector>
#include <bitset>
using namespace std;

constexpr int N = 100 + 1, M = 100 * (100 - 1);

int u[M], v[M], c[N];
vector<pair<int, int>> adj[N];
bitset<N> vis;
bitset<M> way;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;
    for (int i = 0; i < m; ++i) {
        cin >> u[i] >> v[i];
    }
    for (int i = 1; i <= n; ++i) {
        cin >> c[i];
    }
    for (int i = 0; i < m; ++i) if (c[u[i]] == c[v[i]]) {
        adj[u[i]].emplace_back(v[i], i << 1);
        adj[v[i]].emplace_back(u[i], i << 1 | 1);
    }

    auto dfs = [](const auto &self, int u) -> void {
        vis[u] = true;
        for (const auto &i : adj[u]) {
            const int &v = i.first, &eid = i.second;
            if (way[eid ^ 1]) {
                continue;//防止一条无向返祖边被标记两次
            }
            way[eid] = true;
            if (!vis[v]) {
                self(self, v);
            }
        }
    };
    for (int i = 1; i <= n; ++i) if (!vis[i]) {
        dfs(dfs, i);
    }

    for (int i = 0; i < m; ++i) {
        if (c[u[i]] == c[v[i]]) {
            cout << (way[i << 1] ? "->\n" : "<-\n");
        } else {
            cout << (c[u[i]] < c[v[i]] ? "<-\n" : "->\n");
        }
    }
    return 0;
}
时空复杂度:O(n+m)O(n + m)

Extra:解决之后的思考

若不保证有解

若题目不保证一定有解,如何判断是否存在解?
着手解决充分性呢? 一节中,可以得知,在满足着手解决的所有必要条件后,对于所有合法的定向,R(i)R(i) 序列始终唯一。
因此可以在定向完所有边后的图中,计算 R(i)|R(i)| 序列,与 cic_i 序列对比即可。
朴素实现的时空复杂度为 O(n+m+n(n+m))O(n + m + n(n + m))。缩点后使用 bitset 加速 dag 上的可达性统计可以做到 O(n+m+n(n+m)w)O(n + m + \frac{n(n + m)}{w}) 的时间,O(n2+m)O(n^2 + m) 的空间,具体实现碍于篇幅不再赘述,洛谷有道例题

一些启示

  • 当从满足充分条件的角度不方便思考时,可以先想想满足必要条件,说不定满足足够多的必要条件后,条件就变充分了
  • 可以从 ChatGPT 那里得知很多小名鼎鼎的定理

后记

  • 感谢 ChatGPT-5.1 与 ChatGPT-5.1 Thinking 对本文严谨性的大力支持(未用其辅助生成本文文字)
由于更新一篇已经被洛谷全站推荐的文章需要重新审核,审核期间文章将被撤下全站推荐;因此若有一些小勘误或补充,会优先在博客园同步发布的本文做出更新,视重要程度在洛谷后更新。欢迎在任意一个平台的本文评论区指出错误和学术讨论!感谢能看到这里的大家!

评论

0 条评论,欢迎与作者交流。

正在加载评论...