Codeforces 1710D Recover the Tree - 构造
题目传送门
因为一些奇奇怪怪的原因被迫去打 acm,sad....
不难注意到一条很简单的性质:如果两个连通区间交非空,那么它们的交和并都是连通的。
考虑把 $1$ 当做根,根据这条性质可以知道剩下极大的连通区间两两无交且并集为 $[2, n]$。
注意到每个极大连续区间可以当做一个子问题,先假设我们递归得到了满足题目条件的这样一些连通块。
现在考虑把它们连接起来,使得满足根的限制,以及它们之间的限制(不存在连通区间)。
对于一个极大连通区间 $[l, r]$
如果存在一个连通区间 $[1, r']$ 满足 $l \leqslant r' \leqslant r$,设 $r_0$ 是满足这个条件最小的 $r'$,那么仅需把 $r_0$ 接到 $1$ 上即可满足所有连通块之间的限制。根据最开始那条性质,也容易知道满足根给它的限制。
如果不存在的话,如果 $[1, l - 1]$ 不是连通区间,那么直接把这个连通块接在 $1$ 上就可以了,否则把它接在 $r_1$ 上,这个 $r_1$ 是最小的满足 $r' > r$ 且 $[1, r']$ 为连通区间的 $r'$。
注意到最后这一种情况可能对满足连通块之间的限制不是那么地显然。但是注意到一个性质,就是 $(r, r_1)$ 之间一定还包含一个极大连续区间,否则话不可能有满足条件的树。
Code
#include <bits/stdc++.h> using namespace std; const int N = 2e3 + 5; int T; int n; char buf[N]; bool f[N][N]; void dividing(int l, int r) { bool flg = true; for (int l0 = l + 1, r0 = r; l0 <= r; l0 = r0 + 1, r0 = r) { while (!f[l0][r0]) r0--; dividing(l0, r0); for (int i = l0; i <= r; i++) { if (f[l][i]) { if (i > r0) { if (flg) { printf("%d %d\n", r0, i); flg = false; } else { printf("%d %d\n", l, l0); } } else { flg = true; printf("%d %d\n", l, i); } break; } } } } void solve() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%s", buf + i); for (int j = i; j <= n; j++) { f[i][j] = (buf[j] == '1'); } } dividing(1, n); } int main() { scanf("%d", &T); while (T--) { solve(); } return 0; }