CF2208D1 Tree Orientation (Easy Version)

· · 题解

D1 切了 D2 没能场切,哈哈。

题目大意

你原本有一棵包含 n 个节点的无向树。为了让这棵树更有趣,你决定给这 n−1 条边中的每一条都任意指定一个方向。

随着时间推移,你忘记了这棵树的原始结构。但你找到了一张纸,上面记载了:在给所有边指定方向后,对于所有满足 1\le u,v\le n 的有序对 (u,v)u 是否能到达 v

你需要根据这张纸上的信息,还原出树的结构以及每条边的方向。

请判断是否存在合法解,并构造出一组解;如果存在多组合法解,输出任意一组即可。

## 解法 先考虑无解的情况。 首先比较显然的就是:如果一个点无法到达自己本身,那么无解。 其次就是:如果存在 $(u,v)$ 为 `1` 并且 $(v,u)$ 也为 `1`,那么无解。因为这样就相当于出现了一个环,而树是不能有环的。 再然后就是:如果存在 $(i,j),(j,k)$ 都为 `1` 并且 $(i,k)$ 为 `0`,那么无解。显然如果一个点能从另一个点转移到第三个点,那么这个点和第三个点是连通的。 那么剩下的就**可能**有解。考虑如何构造,我们先将每个点所能到达的点集存下来,然后依次遍历这些点集,构造每个点的出边即可。 需要注意的是第三种无解的情况的应用:如果 $(i,j),(j,k)$ 都为 `1`,那么就没必要连一条 $(i,k)$ 的边了。 最后还要判断图是否连通,不连通的话肯定就无解。 这部分可以 $O(n^3)$ 完成,`bitset` 优化可以做到 $O(\frac{n^3}{\omega})$。这里给出 `bitset` 优化过后的代码。 ::::success[Code] ```cpp #include <bits/stdc++.h> #define loop(i,a,b) for(register int i=(a);~i;i=(b)) #define Mem(a,b) memset((a),(b),sizeof((a))) #define eb emplace_back #define pb push_back using namespace std; using i64 = long long; using uint = unsigned int; using ui64 = unsigned long long; using i128 = __int128; constexpr int N = 8000 + 15, mod = 998244353; namespace FAST_IO { #define IOSIZE 300000 char ibuf[IOSIZE], obuf[IOSIZE], *p1 = ibuf, *p2 = ibuf, *p3 = obuf; #define getchar() ((p1==p2)and(p2=(p1=ibuf)+fread(ibuf,1,IOSIZE,stdin),p1==p2)?(EOF):(*p1++)) #define putchar(x) ((p3==obuf+IOSIZE)&&(fwrite(obuf,p3-obuf,1,stdout),p3=obuf),*p3++=x) #define isdigit(ch) (ch>47&&ch<58) #define isspace(ch) (ch<33) template <typename T> inline void read (T &x) { x = 0; T f = 1;char ch = getchar ();while (!isdigit (ch)) {if (ch == '-') f = -1; ch = getchar ();}while (isdigit (ch)) {x = (x << 1) + (x << 3) + (ch ^ '0'); ch = getchar ();} x *= f;} template <> inline void read (double &x) { x = 0; int f = 1;char ch = getchar ();while (!isdigit (ch)) { if (ch == '-') f = -1; ch = getchar ();} while (isdigit (ch)) x = x * 10 + (ch - '0'), ch = getchar ();if (ch == '.') {ch = getchar (); for (double t = 0.1; isdigit (ch); t *= 0.1) x += t * (ch - '0'), ch = getchar ();}x *= f;} inline void read(char &x) {char ch;while ((ch = getchar()) != EOF && isspace(ch));if (ch != EOF) x = ch;} inline bool read(char *s) { char ch; while (ch = getchar(), isspace(ch)); if (ch == EOF) return false; while (!isspace(ch)) *s++ = ch, ch = getchar(); *s = '\000'; return true; } inline bool read(string& s) { s = ""; char ch; while (ch = getchar(), isspace(ch)); if (ch == EOF) return false; while (!isspace(ch)) s += ch, ch = getchar(); return true; } template <typename T, typename ...Args> inline void read (T &x, Args &...args) {read(x); read(args...);} template <typename T> inline void write (T x) { if (x < 0) putchar ('-'), x = -x; if (x > 9) write (x / 10); putchar (x % 10 + 48);} inline void write(string x) { for (int i = 0, n = x.size(); i < n; i++) putchar(x[i]); } inline void write(char *x) { while (*x) putchar(*x++); } inline void write(const char *x) { while (*x) putchar(*x++); } inline void write(char x) { putchar(x); } struct fio { ~fio () {if (p3 != obuf) { fwrite(obuf, 1, p3 - obuf, stdout);p3 = obuf;}}} io; template <typename T> fio& operator >> (fio &io, T &x) {return read (x), io;} template <typename T> fio& operator << (fio &io, const T &x) {return write (x), io;} #define cin io #define cout io #define endl '\n' } using namespace FAST_IO; bool MS; int n; string s[N]; int fa[N], rk[N]; int sz[N]; int find (int x) { return x == fa[x] ? x : (fa[x] = find (fa[x])); } void join (int x, int y) { int f1 = find (x), f2 = find (y); if (f1 != f2) { if (rk[f1] < rk[f2]) { fa[f1] = f2; } else { fa[f2] = f1; if (rk[f1] == rk[f2]) { rk[f1] ++; } } } }//其实路径压缩就可以了,这里只是为了卡过 D2(然而最后没卡过) inline void solve () { cin >> n; vector <bitset <N>>bit(n + 1); for (int i = 1; i <= n; ++ i) { cin >> s[i]; for (int j = 1; j <= n; ++ j) { if (s[i][j - 1] == '1') bit[i].set (j); } } for (int i = 1; i <= n; ++ i) if (!bit[i][i]) { cout << "nO" << endl; return; }//自己必须能到达自己 for (int i = 1; i <= n; ++ i) for (int j = i + 1; j <= n; ++ j) if (bit[i][j] && bit[j][i]) { cout << "nO" << endl; return; }//不能出现环 for (int i = 1; i <= n; ++ i) for (int j = 1; j <= n; ++ j) if (bit[i][j] && (bit[j] & ~bit[i]).any ()) { cout << "nO" << endl; return; }//第三种情况 for (int i = 1; i <= n; ++ i) sz[i] = bit[i].count(); vector <int> ans (n); iota (ans.begin(), ans.end(), 1); sort (ans.begin(), ans.end(), [&](int x, int y) { return sz[x] > sz[y]; });//一个点能够到达的节点越多说明它在原树中越浅 vector <pair <int, int>> edges; for (int i = 1; i <= n; ++ i) { bitset<N> dir = bit[i]; dir.reset (i); vector <int> nn; for (int j : ans) { if (j == i) continue; if (bit[i][j]) nn.eb(j); } for (int j : nn) { if (dir[j]) { edges.eb(i, j); dir &= ~bit[j];//去除满足 i->j->k 的所有 i->k 的边 } } } if (edges.size() != n - 1) { cout << "nO" << endl; return; }//不连通 for (int i = 1; i <= n; ++ i) fa[i] = i, rk[i] = 1; for (auto [u, v] : edges) join (u, v); int rt = find(1); for (int i = 2; i <= n; ++ i) if (find(i) != rt) { cout << "nO" << endl; return; }//不连通 cout << "yEs" << endl; for (auto [u, v] : edges) cout << u << " " << v << endl; } bool MT; int main () { int T_T; cin >> T_T; while (T_T --) solve (); return 0; } ``` ::::