Description
给定一棵树,编号为 i 的点权值为 2^i,定义一条边的权值为这条边链接的两个点中深度较大的点的子树的点权和。给出总点数 n,按照边权从大到小给出每条边的深度较浅的点的编号,要求构造原树。
Solution
边权即子树和,那么同一条直链上的边权单调,且根处最大,叶子处最小。因此排在整个序列末尾的一定是叶子结点相关的边。一个结点时叶子结点,当且仅当它不曾在原序列中出现。
考虑一个构造过程,每一步我们会定出一条边,我们一定希望我们要定出的这条边边权最小,因此对于所有叶子结点(挂着他们的子树),维护小顶堆,权值为子树点权和,但是这样太难维护了,所以点权和直接用点自身的编号替代(题目条件的使用)。
我们按原序列倒序遍历所有的点,对于每个点,弹出堆顶与其连边(相当于一个非叶子和叶子的配对过程),配对后的点其实就被“删除”了,这个时候如果产生了新的叶子,那么也需要将其加入堆中。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1000005;
int n,a[N],root,d[N];
priority_queue <int,vector<int>,greater<int>> heap;
signed main()
{
ios::sync_with_stdio(false);
cin>>n;
for(int i=2;i<=n;i++) cin>>a[i];
root=a[2];
cout<<root<<endl;
for(int i=2;i<=n;i++) d[a[i]]++;
for(int i=1;i<=n;i++) if(d[i]==0) heap.push(i);
for(int i=n;i>=2;i--)
{
int p=heap.top();
heap.pop();
cout<<p<<" "<<a[i]<<endl;
d[a[i]]--;
if(d[a[i]]==0) heap.push(a[i]);
}
}