一棵以
1
1
1为根大小为
n
n
n的树,要求父亲编号小于儿子,
2
n
−
2
2n-2
2n−2个数,可任意分配使它们作为树的边权和每个点的父亲编号,求各种分配下
1
1
1到
n
n
n路径长度分别为
[
1
,
n
)
[1,n)
[1,n)时路径的最大边权和。
n
≤
1
0
5
nle10^5
n≤105
题解
树的形态确定后,最大边权和自然为剩余未选的若干个数之和。
统计每个数出现的个数
c
i
c_i
ci,求出前缀和,若存在
s
u
m
i
−
1
<
i
−
1
sum_{i-1}<i-1
sumi−1<i−1则必然无解,因为可选的父亲个数小于儿子个数;若存在
s
u
m
i
−
1
=
i
−
1
sum_{i-1}=i-1
sumi−1=i−1则它
i
i
i必然要作为
1
1
1到
n
n
n路径上的点,不然没有多余的父亲编号让
i
i
i后某个点连向
i
i
i前某个点;其他的点都是在或不在路径上皆可。
否则,先考虑从
1
1
1到
n
n
n路径最长的情况,自然可以把所有
c
i
≠
0
c_i
ot=0
ci�=0作为中间点连接起来,然后剩下的点从前往后选择编号较小的儿子,即可求出当前最大的边权和。
记录下作为答案的数字集合
D
D
D,以及剩余可选的数字集合
C
C
C。
接着考虑减小路径长度,为了剩出更大的数,则从后往前删去可不在路径上的点
x
x
x,
此时因为边数减少了,则从
D
D
D中挑出最小的数删去,放入
C
C
C,重复做两遍这个过程,因为接下来的操作可能可以加入更大的数。
x
x
x和父亲断开了,则把它父亲放入
C
C
C,再把它连向
C
C
C中最小的点,
最后找到
C
C
C中最大的数加入
D
D
D,并从
D
D
D中删去。
C
C
C和
D
D
D用权值线段树维护。
代码
#include<cstdio>#include<cstring>#include<algorithm>usingnamespace std;#define N 100010#define ll long long
ll dis[N], F[N];int n, a[N *2], c[N], d[N], tp[N], fr[N], q[N];struct{int q[N *4];voidadd(int v,int l,int r,int x,int c){if(l == r) q[v]+= c;else{int mid =(l + r)/2;if(x <= mid)add(v *2, l, mid, x, c);elseadd(v *2+1, mid +1, r, x, c);
q[v]= q[v *2]+ q[v *2+1];}}intfind_mx(int v,int l,int r){if(l == r)return l;int mid =(l + r)/2;if(q[v *2+1]>0)returnfind_mx(v *2+1, mid +1, r);returnfind_mx(v *2, l, mid);}intfind_mi(int v,int l,int r){if(l == r)return l;int mid =(l + r)/2;if(q[v *2]>0)returnfind_mi(v *2, l, mid);returnfind_mi(v *2+1, mid +1, r);}voidins(int k,int c){add(1,1, n, k, c);}intans_mx(){returnfind_mx(1,1, n);}intans_mi(){returnfind_mi(1,1, n);}}di, ci;intmain(){int i, j, k;scanf("%d",&n);for(i =1; i <=2* n -2; i++)scanf("%d",&a[i]), c[a[i]]++;memset(dis,255,sizeof(dis));sort(a +1, a + n *2-1);for(i =1; i <= n; i++){
F[i]= F[i -1]+ c[i];if(F[i -1]< i -1||(i < n && F[i -1]== i -1&&!c[i])){for(j =1; j < n; j++)printf("-1 ");return0;}}int sum =0, s =0, la =1;for(i =2; i <= n; i++){if(F[i -1]== i -1|| i == n) tp[i]=1, s++;if(c[i]|| i == n) fr[i]= la, c[la]--, sum++, la = i;}
j =1;for(i =2; i < n; i++)if(!fr[i]){while(!c[j]) j++;
c[j]--, fr[i]= j;}int t = sum;
dis[sum]=0;for(i = n -1; i; i--)while(c[i]&& t) dis[sum]+= i, c[i]--, d[i]++, t--;for(i =1; i < n; i++) di.ins(i, d[i]), ci.ins(i, c[i]);
j = n, k =1;while(--sum >= s){while(tp[fr[j]]) j = fr[j];int x = fr[j];
dis[sum]= dis[sum +1];
k = di.ans_mi();
di.ins(k,-1);
ci.ins(k,1);
dis[sum]-= k;
k = di.ans_mi();
di.ins(k,-1);
ci.ins(k,1);
dis[sum]-= k;
fr[j]= fr[x];
ci.ins(x,1);
k = ci.ans_mi();
ci.ins(k,-1);
fr[x]= k;
k = ci.ans_mx();
di.ins(k,1);
ci.ins(k,-1);
dis[sum]+= k;}for(i =1; i < n; i++)printf("%lld ", dis[i]);return0;}
自我小结
尽管这题有
O
(
n
)
O(n)
O(n)的做法,但这种解法优化到
O
(
n
)
O(n)
O(n)并不容易,另外因为
C
C
C同时需要最小值和最大值,所以用堆维护也比较麻烦,因此在尝试上花费了较多的时间。