HDU 5764 After a Sleepless Night(dfs)

125 篇文章 0 订阅

Description
给出一棵有n个节点的树,每个点有一个点权v[i](v序列为1~n的一个重排),定义a[i]为以i节点为根的子树中所有点的点权最大值,现在只给出a序列,问能否找到一个合法的v序列,如果有,输出字典序最小的那个
Input
第一行一整数T表示用例组数,每组用例首先输入点数n,之后输入n个整数表示a序列,之后n-1行每行两个整数u和v表示u和v之间有一条边(T<=20,n<=100000)
Output
对于每组用例,如果存在合法的v序列满足条件则输出其中字典序最小的那个,否则输出Impossible
Sample Input
2

2
2 2
1 2

3
3 1 2
1 2
2 3
Sample Output
Case #1: 1 2
Case #2: Impossible
Solution
以下称a[i]值为点i的权值,称点i的原先权值v[i]为点i的答案
首先我们知道具有相同权值的点应该是一条连续的链,而权值为n的点一定是一条从某个点开始一直到树根的链,我们可以先找到一个权值为n的点i(如果没有就Impossible),然后从i开始dfs找和其相连且权值同样为n的点,找到最后一个点令其为root1,然后从root1开始重复上述操作找到最后一个点root2,这样一来如果存在合法解,那么从root1到root2一定是原树上一条从根开始的链,由于root1和root2地位等价,为使得答案字典序最小必然选取编号较小的那个点为根节点(不妨令为root),然后编号较大的那个点的答案就是n,之后从root开始对整张图dfs,在dfs过程中可以确定每个节点的父亲节点fa[i],同时开一个数组cnt[i]记录i节点的儿子节点中权值与i节点权值相同的点的个数,如果cnt[i]=0说明i节点的答案就是其权值,如果cnt[i]>1说明有至少两个儿子节点权值和i相同,这显然不符合条件Impossible,如果存在某个儿子节点的权值大于i那么也不符合条件Impossible,这样做一遍之后,每个出现过的权值必然已经被确定位置,即我们已经找到了这条权值链的末端,然后我们从大到小枚举权值,对于一个出现过的权值,我们从其位置开始往父亲节点找权值与其相同的点扔进一个大根堆里(这里用一个优先队列即可,编号大的点优先),说明这些点的答案还没有被确定而且其答案一定小于等于其权值,如果某权值没有出现过,那么从堆顶取出一个位置赋该权值,因为此时将该权值赋给堆中任一元素都是合法的,为保证字典序最小故将较大的权值赋给编号较大的点,如果此时堆已空也是Impossible
Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define maxn 111111
int T,n,fa[maxn],root1,root2,a[maxn],ans[maxn],pos[maxn],cnt[maxn],flag,Case=1;
vector<int>g[maxn]; 
priority_queue< int,vector<int>,less<int> >que;
void init()
{
    flag=1;
    while(!que.empty())que.pop();
    for(int i=0;i<maxn;i++)g[i].clear();
    memset(pos,0,sizeof(pos));
    memset(cnt,0,sizeof(cnt));
    memset(ans,0,sizeof(ans));
}
void dfs0(int u,int f)
{
    root1=u;
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v==f||a[v]!=n)continue;
        dfs0(v,u);
    }
}
void dfs(int u,int f)
{
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v==f)continue;
        fa[v]=u;
        if(a[v]==a[u])cnt[u]++;
        if(a[v]>a[u])flag=0;
        dfs(v,u);   
    }
    if(cnt[u]==0)ans[u]=a[u],pos[a[u]]=u;
    if(cnt[u]>1)flag=0;
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        init();
        scanf("%d",&n);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]),pos[a[i]]=i;
        for(int i=1;i<n;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            g[u].push_back(v),g[v].push_back(u);
        }
        printf("Case #%d:",Case++);
        if(!pos[n])
        {
            printf(" Impossible\n");
            continue;
        }
        dfs0(pos[n],pos[n]);
        root2=root1;
        dfs0(root2,root2);
        if(root1>root2)swap(root1,root2);
        ans[root2]=n,pos[n]=root2;
        fa[root1]=0;
        dfs(root1,root1);
        if(!flag)
        {
            printf(" Impossible\n");
            continue;
        }
        for(int i=n;i>=1;i--)
        {
            if(pos[i])
            {
                int u=fa[pos[i]];
                while(u&&a[u]==i)
                {
                    que.push(u);
                    u=fa[u];
                }
            }
            else 
            {
                if(que.empty())
                {
                    flag=0;
                    break;
                }
                else 
                {
                    int u=que.top();que.pop();
                    ans[u]=i;
                }
            }
        }
        if(!flag)printf(" Impossible\n");
        else
        {
            for(int i=1;i<=n;i++)printf(" %d",ans[i]);
            printf("\n");
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值