F. Tree Restoration
time limit per test:2.0 s
memory limit per test:256 MB
input:standard input
output:standard output
There is a tree of n vertices. For each vertex a list of all its successors is known (not only direct ones). It is required to restore the tree or to say there is no such tree.
Input
The first line contains a single integer n (1 ≤ n ≤ 1000) — the number of vertices in the tree.
Each of the next n lines contains an integer ci (0 ≤ ci ≤ n) — the number of successors of vertex i, and then ci distinct integers aij(1 ≤ aij ≤ n) — the indices of successors of vertex i.
Output
If the answer does not exist, output «NO».
Otherwise, in the first line output «YES», and then output n - 1 lines containing two integers each — indices of parent and child. Pairs (parent, child) can be output in any order.
Examples
input
5
4 2 3 4 5
3 3 4 5
2 4 5
1 5
0
output
YES
1 2
2 3
3 4
4 5
input
5
4 2 3 4 5
3 3 4 5
0
1 5
0
output
YES
1 2
2 3
2 4
4 5
input
3
3 2 3 1
3 3 1 2
3 1 2 3
output
NO
题意:
给出n个节点的信息,对于每个节点信息都有ci个以自己为根所在子树上点的编号,问你是否存在一棵树完全符合所给出的信息。
思路:
类似于拓扑排序,从aij到i建一条有向边且i的入度加1,建完图以后我们分情况讨论:
1.不存在入度为0的点,则有环,输出NO。
2.把入度为0的点扔进队列,跑拓扑序。取出入度最小的点,并在删边的时候把入度为0的点扔进队列。删完与队头节点相连的边,那么这个队头节点一定和入度最小的点有一条边。跑拓扑序过程当中如果存在入度一样的点则直接break判NO或者break出循环判建的新边数是否等于n-1。(仔细想想如果存在入度一样的点会发生什么,入度一样的这些点实际上可以看做队头节点的候选父亲)
3.队列跑完如果边数不等于n-1输出NO。
4.还存在一种情况,建出来的树和所给信息不完全一致,这个时候需要对新得到的这些边建一个图去核对所给的信息。
示例程序:
#include <bits/stdc++.h>
#define QAQ 0x3f3f3f3f
using namespace std;
struct jj
{
int v,next;
}w[1000000];
struct kk
{
int u,v;
}ans[1000];
int n,h[1000],numw,in[1000];
vector<int>v1[1000],v2[1000];
void insert(int u,int v)
{
w[numw].v=v;
w[numw].next=h[u];
h[u]=numw++;
}
void dfs(int pos,int fa)
{
int i,i1;
for(i=h[pos];i!=-1;i=w[i].next)
{
if(w[i].v!=fa)
{
dfs(w[i].v,pos);
for(i1=0;v2[w[i].v].size()>i1;i1++) //记录子树的节点
{
v2[pos].push_back(v2[w[i].v][i1]);
}
v2[pos].push_back(w[i].v);
}
}
}
int judge()
{
int i,i1;
memset(in,0,sizeof(in));
memset(h,-1,sizeof(h));
numw=0;
for(i=0;n-1>i;i++)
{
insert(ans[i].u,ans[i].v); //建图
in[ans[i].v]++;
}
for(i=0;n>i;i++)
{
if(in[i]==0)
{
break; //找根结点
}
}
dfs(i,-1);
for(i=0;n>i;i++) //核对信息
{
sort(v2[i].begin(),v2[i].end());
if(v1[i].size()!=v2[i].size())
{
return 0;
}
for(i1=0;v2[i].size()>i1;i1++)
{
if(v1[i][i1]!=v2[i][i1])
{
return 0;
}
}
}
return 1;
}
int main()
{
int i,i1,v,flag,top,pos;
queue<int>q;
scanf("%d",&n);
memset(h,-1,sizeof(h));
numw=0;
top=0;
for(i=0;n>i;i++)
{
scanf("%d",&in[i]);
for(i1=0;in[i]>i1;i1++)
{
scanf("%d",&v);
insert(v-1,i);
v1[i].push_back(v-1);
}
if(in[i]==0)
{
q.push(i);
}
sort(v1[i].begin(),v1[i].end()); //这里对节点信息排序是对于情况4方便进行对比
}
if(q.empty()==1)
{
puts("NO");
}
else
{
while(q.empty()==0)
{
flag=0;
pos=-1;
for(i=h[q.front()];i!=-1;i=w[i].next)
{
in[w[i].v]--;
if(in[w[i].v]==0)
{
q.push(w[i].v);
}
if(pos==-1)
{
pos=w[i].v;
}
else
{
if(in[pos]==in[w[i].v])
{
flag=1; //存在入度相同的点跳出循环
break;
}
else if(in[pos]>in[w[i].v])
{
pos=w[i].v;
}
}
}
if(flag==1)
{
break;
}
if(pos!=-1) //找到点,建边
{
ans[top].u=pos;
ans[top++].v=q.front();
}
q.pop();
}
if(top!=n-1) //边数是否符合
{
puts("NO");
}
else if(judge()==0) //是否与给出信息完全一致
{
puts("NO");
}
else
{
puts("YES");
for(i=0;n-1>i;i++)
{
printf("%d %d\n",ans[i].u+1,ans[i].v+1);
}
}
}
return 0;
}