題意
大致意思就是有一棵還未構建出來的樹。
然後給出一個表示這棵樹第i個點到所有點的距離和。
且保證互不相同,要求構造出這棵樹。
思考歷程
題意清新簡短。
然鵝真滴噁心。
一開始成功發現一個性質,Di最小的點即爲重心。
然後考慮將它作爲一個根來搞。
然後就搞到頭穿都沒搞出來。
心態大崩。
題解
看完題解,猶如醍醐灌頂。
只能大呼妙哉。
重心確實可以當數的根,但是從根往周圍拓展顯然是個令人智熄的想法。
題解是反過來,從葉子推向根。
我們發現,由於d是不會重複的,而且一定有一個葉子節點的d是最大的。
抓住這個性質,我們就從大到小拍個序。
於是我們就從這個葉子節點開始。
我們發現,當前葉子節點的父親只可能是:
其中表示i的子樹大小。
意思其實很顯然,類似於換根思想。
那麼抓住這個性質我們就可以從大到小開始構造。
從大到小枚舉,每次一個點我們用hash或別的什麼找出它的父親,標記,連邊,維護siz。
然後如果一個點沒被標記,則這個點也是個葉子節點,一起找父親即可。
這樣構造就沒了。
注意兩個東東:
1、如果在構造的時候找不到父親或父親是自己,那麼就輸出-1.
2、在構造完的時候還要從根開始走一遍判斷根的d合不合法,因爲邪惡的出題人可能會將所有的d加個數,然後就發現構造沒問題,但距離卻多了些詭異的東西。
不理解可以看看這個數據:
7
11
16
14
19
12
15
20
標程
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;
const long long mo=50000007;
const int maxn=200010;
int n,m,id[maxn],fa[maxn];
long long d[maxn],siz[maxn],ans,js[maxn];
int tot,nex[maxn*2],las[maxn*2],tov[maxn*2];
int hs[mo+10];
void con(int x,int y)
{
tot++;
tov[tot]=y;
nex[tot]=las[x];
las[x]=tot;
}
void insert(long long x,int id)
{
long long i=x%mo;
while (hs[i]!=0)
{
i++;
if (i==mo) i=0;
}
hs[i]=id;
}
long long find(long long x)
{
long long i=x%mo;
while (hs[i]!=0 && d[hs[i]]!=x)
{
i++;
if (i==mo) i=0;
}
return hs[i];
}
void qsort(int l,int r)
{
int i=l;int j=r;
long long m=d[(i+j)/2];
while (i<=j)
{
while (d[i]>m) i++;
while (d[j]<m) j--;
if (i<=j)
{
swap(d[i],d[j]);
swap(id[i],id[j]);
i++;j--;
}
}
if (l<j) qsort(l,j);
if (r>i) qsort(i,r);
}
void dfs(int x,int ff)
{
js[x]=js[ff]+1;
for (int i=las[x];i;i=nex[i])
{
if (tov[i]!=ff)
{
dfs(tov[i],x);
ans+=js[x];
}
}
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%lld",&d[i]);
id[i]=i;
siz[i]=1;
}
qsort(1,n);
for (int i=1;i<=n;i++)
{
insert(d[i],i);
}
for (int i=1;i<n;i++)
{
long long op=d[i]-(n-2*siz[i]);
long long oq=find(op);
if (oq==0 || oq==i)
{
printf("-1\n");
return 0;
}
else
{
fa[i]=oq;
siz[oq]+=siz[i];
con(i,oq);con(oq,i);
}
}
dfs(n,0);
if (ans!=d[n])
{
printf("-1\n");
return 0;
}
else
{
for (int i=1;i<n;i++)
{
printf("%d %d\n",id[i],id[fa[i]]);
}
}
}