分析(官方题解):
假设根已确定,可以发现新树若合法,需满足以下性质:根节点是n;儿子的值不大于父亲;具有相同值的节点形成一条链,并且链不会发生“分叉”(即有多个最低点)。所以对于新树中有出现的值x,原树在新树x链的最低点应为x,而其他新值为x的点,原值应小于x。那么我们先将所有链的最低点放上对应值,而空着的点和还没用的值进行配对。 贪心使答案字典序最小:从大到小枚举未用的值,用大根堆维护该值可以填入的位置id,取最大id填入。(否则,若某一步的值a匹配了非最大id x,而最大id y在后面配了值b,那么交换xy将产生更优解)。 还有一种方法是从大到小枚举空位置,填入 最接近小于 该位置新值的 未用的值。这用set是容易实现的。并且队友想出了使用并查集+双向链表的写法,可做到并查集复杂度(O(n*a(n)))。 不合法的情况除了不满足上述性质,还有就是匹配的过程中出现值或位置不够用。
然后考虑根的选择。由上所述,根的值一定是新树n链的两个端点之一。可以发现,无论选哪个做根,不影响其它链的上下方向及链之间的相对关系,即不影响合法性。因为没选的那一头一定填n,所以我们贪心地选择id小的端点做根(否则交换两个端点的值,将得到更优的答案)。当然也可以两个点都跑一遍。 (出题人写hint的时候,把自己绕晕了。不好意思。)
一点感想:这个题就是说原树是有根树(每个点有权值),新树每个节点的权值是其子树的最大值
由于是排列(也就是每个权值都不一样),所以只有链状,具体的分析见这一篇
http://blog.csdn.net/bblss123/article/details/52058959
#include <cstdio> #include <cstring> #include <vector> #include <queue> #include <algorithm> using namespace std; typedef long long LL; const int N = 1e5+5; int head[N],tot; struct Edge{ int v,next; }edge[N<<1]; void add(int u,int v){ edge[tot].v=v; edge[tot].next=head[u]; head[u]=tot++; } int val[N],ret[N],d[N],n,T,color[N],fa[N],kase; vector<int>s; bool can[N]; bool dfs(int u){ int sp=0; for(int i=head[u];~i;i=edge[i].next){ int v=edge[i].v; if(v==fa[u])continue; if(val[v]>val[u])return false; else if(val[v]==val[u]){if((++sp)>1)return false;} fa[v]=u; if(!dfs(v))return false; } if(!sp){color[val[u]]=u;ret[u]=val[u];can[val[u]]=false;} return true; } bool solve(){ s.clear();scanf("%d",&n);tot=0; for(int i=1;i<=n;++i)color[i]=head[i]=-1,fa[i]=d[i]=0,can[i]=true; for(int i=1;i<=n;++i){ scanf("%d",&val[i]); if(val[i]==n)s.push_back(i); } for(int i=1;i<n;++i){ int u,v;scanf("%d%d",&u,&v); add(u,v);add(v,u); if(val[u]==n&&val[v]==n)++d[u],++d[v]; } if(!s.size())return false; for(int i=1;i<s.size();++i) if(d[s[i]]<d[s[0]]||d[s[i]]==d[s[0]]&&s[i]<s[0]) swap(s[i],s[0]); if(d[s[0]]>1)return false; if(!dfs(s[0]))return false; priority_queue<int>q; for(int i=n;i>0;--i){ if(can[i]){ if(q.empty())return false; ret[q.top()]=i;q.pop(); } if(color[i]!=-1){ int tmp=color[i]; while(val[fa[tmp]]==i){ q.push(fa[tmp]); tmp=fa[tmp]; } } } return true; } int main(){ scanf("%d",&T); while(T--){ printf("Case #%d:",++kase); if(!solve())printf(" Impossible "); else { for(int i=1;i<=n;++i)printf(" %d",ret[i]); printf(" "); } } return 0; }