题意简述
给定 \(n\) 个互不相同的整数 \(D_i\),表示一棵树每一个节点到其他所有节点的简单路径距离和。
构造出原树,或判断这是不可能的。
\(n\le 10^5\)。
算法分析
乍看上去没有任何思路,考虑先瞎找一些性质。
首先我们考虑两个相邻的节点 \(x,y\),并假设 \(y\) 是 \(x\) 的父节点,那么不难发现 \(D(x)=D(y)-siz(x)+\left(n-siz(x)\right)\),即 \(D(x)=D(y)-2siz(x)+n\) 。
由这个性质,我们不难发现,所有 \(D_i\) 中最大的一定是一个叶子节点的 \(D_i\) 。
因此我们考虑将这棵树自底向上构造出来。首先先对 \(D_i\) 从大到小排序,然后维护一个森林,存储每一棵树的根节点,树大小,树根的 \(D_i\) ,这样我们可以求出每一棵树要连接的父节点的 \(D_i\)。对于一个新加的节点,我们找到所有可以作为其儿子的树,连边即可。最后连 \(n-1\) 条边即可。
下面我们考虑证明这个构造的正确性。首先我们要证明对于任意一棵树,都能找到一个节点,使得从这个节点所有叶子的 \(D_i\) 递增。
\(D_i\)的递推式与 \(siz\) 有关,因此我们直接取树的重心,由树的重心性质不难发现,其除了其本身以外所有节点的 \(2\times siz<n\),因此 \(D(x)>D(y)\)。
接着我们要证明,如果有解,上面的构造一定能得到一个可行解。
由于 \(D_i\) 互不相同,所以如果对于当前枚举的节点 \(i\) ,有一棵树的根 \(x\) 满足连边条件却不连边,以后一定没有连边的机会,因此一定要连边才可能有解。这样的构造从根到叶子 \(D_i\) 递增,由上证单调性可知对于任意有解的树,这样的构造一定能将其构造出来。
但由于一些边界问题,上述构造得到的解不一定满足题意,我们需要再检验一次。
找连边的节点可以用 set
维护,每一个元素会进入离开set一次,均摊下来复杂度为 \(O(n\log n)\)。
代码实现
#include<bits/stdc++.h>
using namespace std;
#define maxn 1000005
#define maxm 2000005
#define inf 0x3f3f3f3f
#define int long long
#define mod 1000000007
#define local
void file(string s){freopen((s+".in").c_str(),"r",stdin);freopen((s+".out").c_str(),"w",stdout);}
template <typename Tp> void read(Tp &x){
int fh=1;char c=getchar();x=0;
while(c>'9'||c<'0'){if(c=='-'){fh=-1;}c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c&15);c=getchar();}x*=fh;
}
int n,m;
struct node{
int id,vl;
bool operator <(node y)const{
return vl>y.vl;
}
}a[maxn];
int edf[maxn],edt[maxn],ett;
struct nds{
int rt,siz,vl;
bool operator <(nds y)const{//按照应该的父节点di排序
return ((vl+2*siz-n)==(y.vl+2*y.siz-n))?(rt<y.rt):((vl+2*siz-n)<(y.vl+2*y.siz-n));
}
};
set<nds>st;
namespace CHK{//二次检验
vector<int>G[maxn];
int d[maxn],siz[maxn];
void dfs1(int x,int pa){
siz[x]=1;
for(auto y:G[x]){
if(y==pa)continue;
dfs1(y,x);
siz[x]+=siz[y];
d[x]+=d[y]+siz[y];
}
}
void dfs2(int x,int pa){
for(auto y:G[x]){
if(y==pa)continue;
d[y]=d[x]-2*siz[y]+n;
dfs2(y,x);
}
}
void chk(){
for(int i=1;i<=ett;++i){
G[edf[i]].push_back(edt[i]);
G[edt[i]].push_back(edf[i]);
}
dfs1(1,0);dfs2(1,0);
for(int i=1;i<=n;++i){
int x=a[i].id,dd=a[i].vl;
if(d[x]!=dd){
puts("-1");
exit(0);
}
}
}
}
signed main(){
read(n);
for(int i=1;i<=n;++i){
read(a[i].vl);a[i].id=i;
}
sort(a+1,a+n+1);
set<nds>::iterator itl,itr,it,it0;
for(int i=1;i<=n;++i){
if(!st.size()){
st.insert((nds){a[i].id,1,a[i].vl});
continue;
}
int tvl=a[i].vl,tn=(n>>1);
if(n&1)++tvl;//这里是用set写不太方便的一个地方,这里强行构造出一个siz,vl使得vl+2*siz-n=di
itl=st.lower_bound((nds){0,tn,tvl});
itr=st.upper_bound((nds){n+1,tn,tvl});
if(itl==itr){
st.insert((nds){a[i].id,1,a[i].vl});
continue;
}
int sm=1;
for(it=itl;it!=itr;++it){
++ett;
edf[ett]=a[i].id;
edt[ett]=(*it).rt;
sm+=(*it).siz;
}
st.insert((nds){a[i].id,sm,a[i].vl});
for(it=itl;it!=itr;){
it0=it;++it;
st.erase(it0);
}
}
if(st.size()>1){
puts("-1");
}
else{
CHK::chk();
for(int i=1;i<=ett;++i)printf("%lld %lld\n",edf[i],edt[i]);
}
return 0;
}