Gym - 101755F F. Tree Restoration

本文介绍了一种树还原算法,用于根据给定的节点信息判断是否存在一棵树完全符合所给信息。文章详细阐述了算法思路,包括如何通过类似拓扑排序的方法构建树结构并验证其正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值