- 有一棵\(n\)个点的树,乱序给出与每个点距离小于等于\(2\)的点集。
- 求构造一棵合法的树。
- \(n\le10^3\)
非叶节点间的连边
两个非叶节点\(x,y\)之间存在边,则对于它们两侧的两点\(i,j\),同时与\(i,j\)距离小于等于\(2\)的点只有\(x,y\)两点。
因此,非叶节点\(x,y\)之间有边的充要条件就是存在两个点集的交集恰好是\(\{x,y\}\)。要求这个,只需用枚举一对点集用\(bitset\)优化即可。
叶节点的连边
对于叶节点,显然它对应的点集是所有包含它的点集中最小的那个(可能有多个最小的,是和它同父节点的兄弟们,肯定都一样)。
而要找它的父节点,则它点集中所有的非叶节点应该恰好是它父节点的连边集合(加上父节点本身)。因此只要找到一个点的连边集合和修改后的点集相同,这个点就是它的父节点。
非叶节点很少时的判断
首先,如果没有非叶节点,则\(n=2\),直接判断即可。
其次,如果非叶节点数量为\(1\),那么这是一张菊花图,所有点的点集都是\(1\sim n\),很容易判断出来,然后只要随便找个点做菊花图的中心即可。
再次,如果非叶节点数量为\(2\),先按照之前的方法求出这两个叶节点,那么点集可以被划分成两部分,随便分给这两个点即可。
代码:\(O(\frac{n^3}{32})\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000
using namespace std;
int n,p[N+5],w[N+5][N+5];bitset<N+5> s,S[N+5],E[N+5];
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void write(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);}
Tp I void writeln(Con Ty& x,Con Ty& y) {write(x),pc(' '),write(y),pc('\n');}
}using namespace FastIO;
int main()
{
#define Exit return clear(),0
RI i,j,x,y;for(read(n),i=1;i<=n;++i) for(read(x);x;--x) read(y),S[i].set(y);if(n==2) {writeln(1,n);Exit;}//特判无叶节点
for(i=1;i<=n;++i) {for(j=1;j<=n&&S[i].test(j);++j);if(j<=n) break;}if(i>n) {for(i=2;i<=n;++i) writeln(1,i);Exit;}//特判一个叶节点
RI t=0;for(i=1;i<=n;++i) for(j=i+1;j<=n;++j) (s=S[i]&S[j]).count()==2&&(x=s._Find_first(),//枚举每对点集求交
y=s._Find_next(x),!w[x][y]&&(writeln(x,y),E[x].set(y),E[y].set(x),p[x]=p[y]=w[x][y]=w[y][x]=1,++t));//一对非叶节点间的连边
if(t==1) {for(i=1;i<=n&&S[i].count()==n;++i);for(j=1;j<=n;++j) !p[j]&&(writeln(S[i].test(j)?x:y,j),0);Exit;}//特判两个叶节点
for(i=1;i<=n;++i) p[i]&&(E[i].set(i),0);for(i=1;i<=n;++i) if(!p[i])//枚举每个非叶节点找父节点
{
for(t=0,j=1;j<=n;++j) S[j].test(i)&&(!t||S[j].count()<S[t].count())&&(t=j);//找所在最小点集
for(s.reset(),j=1;j<=n;++j) S[t].test(j)&&p[j]&&(s.set(j),0);for(j=1;j<=n;++j) if(E[j]==s) {writeln(i,j);break;}//删去叶节点找父节点
}return clear(),0;
}