[思维构造] 题解 Distance Sums
[思维构造] 题解 Distance Sums
题目分析
注意到给出的数是互不相同的,这点是很重要的。
考虑重心满足所有点到重心的距离和最小,所以先排个序, \(D_i\) 最小的就是重心节点。
考虑以重心为根,得出每个点的子树大小 \(\mbox{sz}(i)\) ,如果 \(u\) 是 \(v\) 的父亲,那么就会有 \(D_v=D_u+n-2\mbox{sz}(v)\) 。
注意到 \(D_i\) 最大的点肯定是叶子节点,也就是说它的 \(\mbox{sz}\) 是固定的,所以按 \(D_i\) 从大到小给每个节点找到它的父亲,由于 \(D_i\) 互不相同,从大到小为每个节点安排父亲节点是他们的父亲节点是固定的,所以每个节点的 \(\mbox{sz}\) 也是固定的,于是我们就可以构造出这样一棵树了,最后还需要判断一下所有点到重心的距离是否满足条件。
找父亲可以二分做,时间复杂度是 \(\mathcal O(n\log_2n)\) 的。
参考代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ch() getchar()
#define pc(x) putchar(x)
using namespace std;
template<typename T>void read(T&x){
static char c;static int f;
for(c=ch(),f=1;c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c>='0'&&c<='9';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>void write(T x){
static char q[65];int cnt=0;
if(x<0)pc('-'),x=-x;
q[++cnt]=x%10,x/=10;
while(x)
q[++cnt]=x%10,x/=10;
while(cnt)pc(q[cnt--]+'0');
}
const int maxn=100005;
struct Node{
long long dis;int id;
Node(long long dis=0,int id=0):
dis(dis),id(id){}
bool operator < (const Node o)const{
return dis<o.dis;
}
}P[maxn];
int sz[maxn];
int Find(int l,int r,long long dis){
while(l<r){
int mid=(l+r)>>1;
if(P[mid].dis<dis)l=mid+1;else r=mid;
}
return P[l].dis==dis?l:-1;
}
int pa[maxn],dp[maxn];
int main(){
int n;read(n);
for(int i=1;i<=n;++i){
read(P[i].dis);P[i].id=i;
}
sort(P+1,P+n+1);
for(int i=n;i>=2;--i){
++sz[i];
long long sd=P[i].dis-(n-sz[i]*2);
int j=Find(1,i-1,sd);
if(~j){
pa[P[i].id]=P[j].id;
sz[j]+=sz[i];
}
else return puts("-1"),0;
}
long long sum=0;
dp[P[1].id]=0;
for(int i=2;i<=n;++i)
sum+=dp[P[i].id]=dp[pa[P[i].id]]+1;
if(sum!=P[1].dis)return puts("-1"),0;
for(int i=2;i<=n;++i)
write(P[i].id),pc(' '),write(pa[P[i].id]),pc('\n');
return 0;
}