@codefoces - 566E@ Restoring Map
@description@
对于一棵 n 个点的树,我们称两个点是相邻的当且仅当两个点的距离 <= 2。
现在给定 n 个集合,每一个集合表示树上某个点的相邻点是哪些。不过你不知道哪个集合对应哪个点。
现在这些集合构造出原树。保证至少存在一个解。如果多解,输出任意解即可。
@solution@
两个点 x, y 对应的集合交集在树上仍然是一个连通块。
如果两个点 x, y 在树上距离 > 4,则它们集合交集为空。
如果两个点 x, y 在树上距离 = 4,则它们集合交集大小为 1。
如果两个点 x, y 在树上距离 = 3,则它们集合交集大小为 2。
如果两个点 x, y 在树上距离 = 2,则它们集合交集大小 >= 3。
如果两个点 x, y 在树上距离 = 1,则它们集合交集大小取决于 n:n = 2 时交集大小为 2;否则交集大小 >= 3。
集合交集为 2,该交集就对应着树上的一条边。
我们用 bitset 维护集合,并通过取交集找出所有的这种类型的边。
如果找不到一条这种类型的边,则只能是菊花图。特判即可。
否则,因为 n = 2 则已经找出了所有边,所以我们现在默认 n ≠ 2。
此时找出的边其实就是非叶结点之间形成的连通块,不妨称为新树。我们现在只需要把叶结点(度数为 1 的点)拼到父亲上面即可得到原树。
我们对于每个非叶结点,求出在新树中距离 <= 1 的点集,称为新邻集。
原图中叶结点原邻集 = 与该叶结点父亲相同的叶结点集合 + 父亲的新邻集。
如果新树只有一条边,此时两个点的新邻集相同,因此需要特判(父亲不同的叶结点分别连两个点)。
否则,每个点的新邻集互相不同。因此我们可以把原邻集与新树取交集,找到那个唯一的父亲。
不过注意到,当某个点在新树中度数为 1,此时它的原邻集与新树取交集也会对应一个新邻集。
此时该点一定连向某个原图中叶结点,且该叶结点的原邻集是该点的真子集。因此我们再判一下是否存在某个点的原邻集是当前点的原邻集的真子集即可。
复杂度 \(O(\frac{n^3}{\omega})\),实际上跑得挺快。
@accepted code@
#include <bitset>
#include <cstdio>
#include <algorithm>
using namespace std;
int tag[1005], n;
bitset<1005>a[1005], b[1005], c[1005], t, p;
int main() {
scanf("%d", &n);
for(int i=1;i<=n;i++) {
int k, x; scanf("%d", &k);
for(int j=1;j<=k;j++)
scanf("%d", &x), b[i][x] = 1;
}
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++) {
t = (b[i] & b[j]);
// printf("%d %d : %d\n", i, j, t.count());
if( t.count() == 2 ) {
int x = t._Find_first(); t[x] = 0;
int y = t._Find_first();
a[x][y] = a[y][x] = 1;
}
}
t = 0;
for(int i=1;i<=n;i++)
if( a[i].count() ) tag[i] = -1, t[i] = 1;
for(int i=1;i<=n;i++) a[i][i] = 1;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if( a[i][j] ) printf("%d %d\n", i, j);
if( t.count() == 0 ) {
for(int i=2;i<=n;i++)
printf("%d %d\n", 1, i);
}
else if( t.count() == 2 ) {
for(int x=1;x<=n;x++) {
for(int y=x+1;y<=n;y++) {
if( a[x][y] ) {
bool flag = false;
for(int i=1;i<=n;i++)
if( b[i].count() != n ) {
for(int j=1;j<=n;j++)
if( b[i][j] ) {
if( tag[j] == 1 ) break;
else if( tag[j] != -1 ) {
printf("%d %d\n", flag ? y : x, j);
tag[j] = 1;
}
}
flag = true;
}
}
}
}
}
else {
for(int i=1;i<=n;i++) {
c[i] = (b[i] & t);
bool flag = true;
for(int j=1;j<=n;j++)
if( b[i] == b[j] ) {
if( j < i ) {
flag = false;
break;
}
}
else if( (b[i] & b[j]) == b[j] ) {
flag = false;
break;
}
if( flag ) {
for(int j=1;j<=n;j++)
if( c[i] == a[j] ) {
for(int k=1;k<=n;k++)
if( b[i][k] && tag[k] == 0 )
printf("%d %d\n", j, k), tag[k] = 1;
break;
}
}
}
}
}
@details@
bitset 真好用。
一开始还想着二分图匹配找每个集合对应原树中哪一个点,不过发现找出来并不会更好做。