UOJ134 【UR #9】App 管理器 题解
题目链接:#134. 【UR #9】App 管理器
题意:
iOS 用户和 Android 用户一定对 App 管理器不会陌生。
“啊啊啊!手机空间又不够了” 买了低端机的 cxy 欲哭无泪。
cxy 常有的文件格式一共有 $n$ 种,分别编号为 $1, \cdots, n$。 cxy 有 $m$ 个 App,分别编号为 $1, \cdots, m$。其中 $i$ 号 App 可以把 $a_i$ 号文件格式转换成 $b_i$ 号文件格式,或者把 $b_i$ 号文件格式转换成 $a_i$ 号文件格式。
App 管理器最近推出了一个新功能:卸载一半!于是 cxy 决定把每个 App 都卸载一半。
对于 $i$ 号 App,你可以决定你卸载哪一半:
- 把 $a_i$ 号文件格式转换成 $b_i$ 号文件格式。
- 把 $b_i$ 号文件格式转换成 $a_i$ 号文件格式。
cxy 希望,把所有 App 都卸载一半之后并不影响使用。即,对于任意两个文件格式 $i, j$,仍能通过一个或多个 App 把 $i$ 号文件格式直接或间接转换成 $j$ 号文件格式。
现在 cxy 已经把部分 App 卸载一半了,请你给出一组卸载剩下的 App 的方案满足条件。
省流:一个混合图,把里面的无向边都定向,使得定向之后的图强连通,求一个方案。
输入格式:
第一行两个整数 $n, m$,表示文件格式的数量和 App 的数量。保证 $n \geq 1, m \geq 0$。
接下来 $m$ 行,每行三个整数 $a_i, b_i, t_i$,表示一个 App。
如果 $t_i = 0$ 则表示这个 App 还没被卸载一半,如果 $t_i = 1$ 则表示这个 App 已经被卸载一半,现在只能把 $a_i$ 号格式转换为 $b_i$ 号格式。
保证 $1 \leq a_i, b_i \leq n$ 且 $a_i \neq b_i$。
输出格式:
输出 $m$ 行,每行一个整数表示卸载的是哪一半。
如果这个 App 还没被卸载一半,输出 0 表示保留 “把 $a_i$ 号文件格式转换成 $b_i$ 号文件格式” 的这一半,否则表示保留 “把 $b_i$ 号文件格式转换成 $a_i$ 号文件格式” 的这一半。
如果这个 App 已经被卸载一半,那么直接输出 0。
保证至少存在一组方案。
数据范围:
$n,m \le 5000$ 。
首先考虑全为无向边的情况我们如何对其定向。
显然这个图一定为边双连通图,否则无论怎么定向都不行,而题目保证有解。
我们直接对这个图 dfs ,然后把树边都定为父亲到儿子的方向,返祖边都定到祖先的方向。
可以证明这样的图一定是强连通的。如果不是,那么必定存在一个点,
它在 dfs 树的后代中,没有返祖边连向这个点的祖先,那么原图就不是双连通图了,矛盾。
然后考虑混合图的情况。显然这个图一开始就是强连通的状态。
考虑一条无向边 $(u,v)$
- 如果从 $u$ 到 $v$ 存在一条不经过这条边的路径,那么我们可以把这条无向边定向成 $v \to u$ 。
- 如果从 $v$ 到 $u$ 存在一条不经过这条边的路径,那么我们可以把这条无向边定向成 $u \to v$ 。
假设这两种情况都不满足,不妨设从 $u$ 不经过这条边能到达的节点集合 $S$ 。
由于原图强连通,则 $v$ 也可以不经过 $u$ 到达 $T = V \setminus S$ 里的点
对于 $S$ 里的任何可以到达 $v$ 的节点,例如某个点 $s$ 到 $v$ 不用经过 $u$ ,
则 $u \leadsto s \leadsto v$ 就是一个不经过那条边的路径,这与假设矛盾。
所以 $S$ 里的任何点都可以不经过 $v$ 到 $u$ ,同理 $T$ 里的点到 $v$ 都不用经过 $u$ 。
考虑 $S$ 和 $T$ 之间的边,除了 $(u,v)$ 外一定还有一条边(双联通)。
不妨假设这条边是从 $S$ 中的 $p$ 到 $T$ 中的 $q$ ,则 $u \leadsto p \to q \leadsto v$ 就是一条不经过 $(u,v)$ 的路径,与假设矛盾。
因此,上述的两种情况必然会出现一种。
我们只需要一次考虑每条边,然后 dfs 出符合哪种情况并定向即可
时间复杂度 $\mathcal{O}(m^2)$
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(5e3 + 15))
#define addEdge(u, v) e[u].push_back(v)
int dfn, u[N], v[N], t[N], vis[N];
list<int> e[N]; list<int>::iterator i1[N], i2[N];
void dfs(int x)
{
vis[x] = dfn;
for(int y : e[x]) if(vis[y] != dfn) dfs(y);
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
int n, m; cin >> n >> m;
for(int i = 1; i <= m; i++)
{
cin >> u[i] >> v[i] >> t[i];
if(t[i]) addEdge(u[i], v[i]);
else {
addEdge(u[i], v[i]); --(i1[i] = e[u[i]].end());
addEdge(v[i], u[i]); --(i2[i] = e[v[i]].end());
}
}
for(int i = 1, j; i <= m; i++)
if(t[i]) cout << "0\n";
else {
e[u[i]].erase(i1[i]); e[v[i]].erase(i2[i]);
++dfn; dfs(v[i]); j = (vis[u[i]] != dfn);
cout << char('0' | j) << '\n';
j ? addEdge(v[i], u[i]) : addEdge(u[i], v[i]);
}
return 0;
}
参考文献:
[1] https://vfleaking.blog.uoj.ac/blog/694
[2] https://yhx-12243.github.io/OI-transit/records/uoj134.html
官方题解里,基图指的是将有向图所有的有向边替换为无向边后得到的无向图。
题外话:
这道题也是2022年欠的题,导致现在写完这题有一种莫名的窒息感。