gmoj 6653. 【2020.05.27省选模拟】树 题解

题目

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 2n2 的序列 a a a

现在题目给出 n n n a a a ,要求你输出 n − 1 n-1 n1 个数。第 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 1n105,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 Si1+1<i 时无解。

再考虑最短的 n → 1 n\to 1 n1 的链会是怎么样的呢?它的长度应该就是不得不选的数的数量,由于这时已经满足 S i − 1 ≥ i − 1 S_{i-1}\ge i-1 Si1i1 ,即 S i ≥ i S_i\ge i Sii 若再有 S i − 1 = i − 1 S_{i-1}=i-1 Si1=i1 i i i 就是必选的了,理由和上一条类似。由于只用关心这条链就好了,链上不可能有两个点的父亲一样,因此每个数最多只选 1 1 1 次。至于为什么这些选择的数一定可以构成一条链,这是因为选出了 p i p_i pi 后,编号为 1 → n − 1 1\to n-1 1n1 的点中哪些点会成为父亲就已经知道了,共有 链的长度 -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;
}

猜你喜欢

转载自blog.csdn.net/huangzihaoal/article/details/114336514