题面描述
已知整数 nnn。fi=∑j=1ndis(i,j)f_i=\sum_{j=1}^n dis(i,j)fi=∑j=1ndis(i,j),已知 f1,f2,⋯fnf_1,f_2, \cdots f_nf1,f2,⋯fn,且对于任意的 1⩽i<j⩽n1 \leqslant i < j \leqslant n1⩽i<j⩽n 满足 fi≠fjf_i ≠ f_jfi=fj。构造一颗由 nnn 个节点构成的树满足对于每个节点的 fff 值与给定的值相同,或输出无解。
1⩽n⩽105,0⩽fi⩽10121 \leqslant n \leqslant 10^5,0 \leqslant f_i \leqslant 10^{12}1⩽n⩽105,0⩽fi⩽1012
时间限制1s,空间限制512MB。
题解
若有解,则 fff 值最小的点一定是树的重心,fff 值最大的点一定是叶子。不妨以重心为根。若点 iii 为点 jjj 的父亲节点,则一定有 fi<fjf_i<f_jfi<fj。当 jjj 为根算出 fjf_jfj 后,换根到 iii 时,iii 到 jjj 之间的边的贡献从 n−sizejn-size_jn−sizej 变为了 sizejsize_jsizej,其他边的贡献不变。因此 fi=fj+2sizej−nf_i=f_j+2size_j-nfi=fj+2sizej−n。将点按 fff 值排序,从 fff 值最大的点开始,找它的父亲并维护其 sizesizesize。通过map等STL可以做到时间复杂度 O(nlog2n)O(n \log_2n)O(nlog2n)。
然而,当 fff 值相对大小不变时,树是可能无解的,因此随便选一个点当根求一次 fff 并与输入的 fff 值进行比对,时间复杂度 O(n)O(n)O(n)。
所以总时间复杂度 O(nlog2n)O(n \log_2n)O(nlog2n),空间复杂度 O(n)O(n)O(n)。
#include<stdio.h>
#include<map>
#include<algorithm>
#include<vector>
using namespace std;
#define R register int
#define L long long
#define N 100001
L f[N],f2[N],q[N];
int fa[N],sz[N];
vector<int>G[N];
inline void Check(int x){
for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
Check(*T);
f2[x]+=f2[*T]+sz[*T];
}
}
int main(){
int n;
scanf("%d",&n);
map<L,int>P;
for(R i=1;i<=n;i++){
scanf("%lld",f+i);
P[f[i]]=i;
q[i-1]=f[i];
sz[i]=1;
}
sort(q,q+n);
for(R i=n-1;i!=0;i--){
int g=P[q[i]];
L t=q[i]+(sz[g]<<1)-n;
if(P[t]==0){
printf("-1");
return 0;
}
fa[g]=P[t];
sz[fa[g]]+=sz[g];
G[fa[g]].push_back(g);
}
int root=P.begin()->second;
Check(root);
if(f2[root]==f[root]){
for(R i=1;i<=n;i++){
if(i!=root){
printf("%d %d\n",i,fa[i]);
}
}
}else{
printf("-1");
}
return 0;
}