题目
https://gmoj.net/senior/#main/show/6653
题目大意:
一棵有 n n n 个节点的以 1 1 1 为根的树,令 p i p_i pi 表示结点 i i i 的父亲编号,那么它还满足 p i < i p_i<i pi<i 。把它每条边的边权以及除 1 1 1 以外每个点的 p i p_i pi 取出,并且将它们 打乱顺序 ,就能得到一个长度为 2 n − 2 2n-2 2n−2 的序列 a a a 。
现在题目给出 n n n 和 a a a ,要求你输出 n − 1 n-1 n−1 个数。第 i i i 个数表示 n n n 到 1 1 1 之间有 i i i 条边时, n n n 到 1 1 1 之间的最小边权总和是多少。如果不存在满足 n n n 到 1 1 1 之间有 i i i 条边的树,输出 − 1 -1 −1 。
满足 1 ≤ n ≤ 1 0 5 , a i ∈ [ 1 , n ) ∪ Z 1\le n\le 10^5,a_i\in [1,n)\cup Z 1≤n≤105,ai∈[1,n)∪Z 。
题解
部分分对正解启发不大,就直接写满分做法了。
把 a a a 放进一个桶里,记这个桶为 B B B ,再用 S S S 表示它的前缀和。
考虑什么时候会无解(即不存在任意一棵操作后能变成 a a a 的树)。由于 p i < i p_i<i pi<i ,即 [ 1 , i ] [1,i] [1,i] 中的每个数都会有一个在 [ 1 , i ) [1,i) [1,i) 中的父亲,因此 S i − 1 + 1 < i S_{i-1}+1<i Si−1+1<i 时无解。
再考虑最短的 n → 1 n\to 1 n→1 的链会是怎么样的呢?它的长度应该就是不得不选的数的数量,由于这时已经满足 S i − 1 ≥ i − 1 S_{i-1}\ge i-1 Si−1≥i−1 ,即 S i ≥ i S_i\ge i Si≥i 若再有 S i − 1 = i − 1 S_{i-1}=i-1 Si−1=i−1 , i i i 就是必选的了,理由和上一条类似。由于只用关心这条链就好了,链上不可能有两个点的父亲一样,因此每个数最多只选 1 1 1 次。至于为什么这些选择的数一定可以构成一条链,这是因为选出了 p i p_i pi 后,编号为 1 → n − 1 1\to n-1 1→n−1 的点中哪些点会成为父亲就已经知道了,共有 链的长度 -1 个点,把我们选出来的数放进这些位置就好了,剩下那个就是叶子。
求长度的话从大到小选择没有用过的数即可。
时间复杂度 O ( n ) O(n) O(n) 。
代码
#include<cstdio>
using namespace std;
#define fo(i,l,r) for(i=l;i<=r;++i)
#define fd(i,r,l) for(i=r;i>=l;--i)
#define N 100005
long long ans[N];
int a[N],b[N],s[N];
bool fixed[N];
inline int mymin(int x,int y){return x<y?x:y;}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
int i,j,l,r,t,x,n,m,len=0;
scanf("%d",&n),m=2*n-2;
fo(i,1,m) scanf("%d",&x),++a[x];
fo(i,1,n-1) ans[i]=-1;
fo(i,1,n-1) s[i]=s[i-1]+a[i];
fo(i,1,n-1)
{
if(s[i-1]+1<i)
{
fo(j,1,n-1) printf("-1 ");
return 0;
}
if(/*s[i]>=i&&*/s[i-1]<=i-1)
fixed[i]=1,--a[i],++len;
}
j=len,ans[len]=0;
fd(r,n-1,1)
{
t=mymin(a[r],j);
a[r]-=t,j-=t,b[r]+=t,
ans[len]+=r*t;
if(!j) break;
}
fo(l,1,n-1) if((a[l]+b[l])&&!fixed[l])
{
fixed[l]=1;
++len,ans[len]=ans[len-1],j=1;
if(!a[l]) ans[len]-=l,++a[l],--b[l],++j;
--a[l];
while(r)
{
t=mymin(a[r],j);
a[r]-=t,j-=t,b[r]+=t,
ans[len]+=r*t;
if(!j) break;
--r;
}
if(len==n-1) break;
}
fo(i,1,n-2) printf("%lld ",ans[i]);
printf("%lld\n",ans[n-1]);
return 0;
}