- 给定一棵\(n\)个点的树中每个点到其余所有点的距离之和(保证互不相同),要求构造一棵合法的树或判无解。
- \(n\le10^5\)
树的重心性质
我们记录每个点的子树大小为\(Sz_x\)。
考虑一个点向上走到所有点距离之和的变化量,到子树内的\(Sz_x\)个点增加了\(1\),到子树外的\(n-Sz_x\)个点减少了\(1\),因此一共变化了\(2\times Sz_x-n\)。
看到距离之和,由于越靠近树的重心到所有点距离之和越小,所以最小的那个距离对应的必然是树的重心,我们不妨以它为根。
这样一来,我们按照距离之和从大到小枚举每个点,那么子节点必然在父节点之前被枚举到。
也就是说,枚举到一个点的时候,已经能确定出\(Sz_x\)了,则到所有点距离之和等于\(D_x+2\times Sz_x-n\)的点就是\(x\)的父节点了(可以用\(map\)简单维护),因为保证距离之和互不相同,这样的点最多只有一个(如果不存在说明无解)。
一波下来我们就确定了树的形态,但还要注意我们只是保证了子节点与父节点距离之和的差值是正确的,最后还要求一下根节点到所有点的距离之和判一下与给出是否相同。
我们可以记录一个\(s_x\)表示每个点到子树内所有点的距离之和,确定父子关系给\(Sz_{fa}\)加上\(Sz_x\)的同时给\(s_{fa}\)加上\(s_x+Sz_x\),则上面一波下来也同时求出了根节点到所有点的距离之和,就不用额外再建树\(dfs\)了。
代码:\(O(nlogn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define LL long long
using namespace std;
int n,fa[N+5],Sz[N+5];LL a[N+5],s[N+5];map<LL,int> p;map<LL,int>::iterator it;
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void write(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);}
Tp I void writeln(Con Ty& x,Con Ty& y) {write(x),pc(' '),write(y),pc('\n');}
}using namespace FastIO;
int main()
{
RI i;for(read(n),i=1;i<=n;++i) read(a[i]),Sz[p[a[i]]=i]=1;//存到map中
RI x;LL t;it=p.end();W(--it,Sz[x=it->second]^n)//按距离之和从大到小枚举
if(2*Sz[x]>=n||!p.count(t=a[x]+2*Sz[x]-n)) return puts("-1"),0;//如果父节点距离之和没有变小或不存在对应距离之和的节点无解
else Sz[fa[x]=p[t]]+=Sz[x],s[fa[x]]+=s[x]+Sz[x];//更新父节点子树大小,更新父节点到子树内所有点的距离之和
if(x=it->second,a[x]^s[x]) return puts("-1"),0;//检验根节点到所有点的距离之和是否正确
for(i=1;i<=n;++i) fa[i]&&(writeln(fa[i],i),0);return clear(),0;
}