[Noip2018]赛道修建

[Noip2018]赛道修建

一.前言

​ 这题的部分分还是给的非常友好!STL真好用!题目链接

二.20pts

​ 这是 (m=1) 的做法,只需要两遍dfs算树的直径就可以了,代码稍有注释。

int est,sume;
void dfs1(int x,int fa,int sumd){
	if(sumd>sume)est=x,sume=sumd;//更新离他最远的点
	for(int i=head[x];i;i=ne[i]){
		if(to[i]==fa)continue;
		dfs1(to[i],x,sumd+dis[i]);
	}
}

if(m==1){
		dfs1(1,0,0);
		sume=0;
		dfs1(est,0,0);//用离1最远的est再来一次
		printf("%d",sume); 
}

三.40pts

​ 这是20分加上链的情况……

​ 给出一种错误思路:直接总和除m(一个特别大就可以hack)

​ 下面是正确的思路,二分一个最短长度埋伏他一手,然后贪心看最多能够凑出几个赛道来。

bool check(int x){
	int cnt=0,sum=0;
	m_for(i,1,n-1){
		if(sum+a[i]>=x)sum=0,cnt++;//又凑出一段
		else sum+=a[i];
	}
	return cnt>=m;
}
//链式向前星存储的,special1是判断是不是链
else if(!spe1){
	m_for(i,1,n-1)
	sume+=(a[i]=(dis[head[i]]!=a[i-1]?dis[head[i]]:dis[ne[head[i]]]));
    //这里偷个懒把长度总和和求出到i+1的长度合在一起写了
	int l=1,r=sume,ans=1;//二分上界为长度总和
	while(l<=r){
		int mid=(l+r)>>1;
		if(check(mid))l=mid+1,ans=mid;
		else r=mid-1;
	}
	printf("%d",ans);
}

三.55pts

就是 (a_i=1) 的菊花图,也是部分分的上限。对于菊花图,我们要么是连着1的两条边凑一起,要么一条边自成一家。这里先 sort 从大到小排个序,然后取前 (2m) 个来拼赛道,按照最大配最小的来。这样可以使得赛道的最短长度最大((2m>n) 时相当于接上空气赛道,长度为0,也就是自成一家,不影响

else if(!spe2){
	for(int i=head[1];i;i=ne[i])a[to[i]]=dis[i];
	sort(a+1,a+n+1,cmp);
	int ans=1<<29;
	for(int i=1;i<=m;++i)ans=min(ans,a[i]+a[(m<<1)-i+1]);
	printf("%d",ans);
}

四.100pts

​ 本片题解的重头戏,满分思路来啦!

​ 首先还是二分长度埋伏他一手,凑出尽可能多的赛道。题目给出的大背景是一个树,我才用深搜,很显然的,遍历到一个点,我先让儿子们只凭借他们所在的子树凑出尽可能多的赛道来。必然的,儿子不可能将他们身上所有的边都用完,此时出现了若干条还没用的边,如图

现在在红点,让蓝点完成了任务,蓝点反馈说我还有三条黑边没有用上,请求父亲帮助!也许这三条黑边任意一条加上红边就可以凑出赛道,又或许加上红边还不行,还要和蓝边的兄弟再操作一手才行……

​ 但是不管怎么样,这三条黑边都必须加上红边才可能有机会。并且在一条黑边获得宠幸后其他的黑边就再也没有机会了。本着贪心的原则,我们追求尽量多的赛道,那么肯定是从这三条黑边当中选一条最长的出来和红边相接。于是儿子做完任务后还需要返回一个最长的没有用到的假赛道。

​ 接下来考虑如何算出只依靠自己的子树的赛道数。很显然,儿子们都反馈给我了一些最长的假赛道,我从中将假赛道接起来就行。还剩下的无用假赛道选一个最长的返回给父亲。如此,这题就做完了。

int dfs(int x,int fa,int &alf,int mid){
	int ans=0;
	multiset<int> s;//保存假赛道
	for(int i=head[x];i;i=ne[i]){
		if(to[i]==fa)continue;
		int k=0;//儿子的最长假赛道
		ans+=dfs(to[i],x,k,mid);//让儿子先做任务
		k+=dis[i];//和红边配起来
		if(k>=mid)ans++;//最好直接成为赛道
		else s.insert(k);//不行就放入假赛道集合中
	}
	while(!s.empty()){
		int k=*s.begin();//取一个最小假赛道
		s.erase(s.begin());//直接扔掉
		multiset<int>::iterator it=s.lower_bound(mid-k);//能配起来的最小假赛道
		if(it!=s.end()){ans++;s.erase(it);}//找得到就拼起来,然后删掉
		else alf=max(alf,k);//记录最长无用假赛道
	}
	return ans;
}
else{
	dfs1(1,0,0);
	sume=0;
	dfs1(est,0,0);
	int r=sume,l=1,ans;//二分上界为树的直径
	while(l<=r){
		int mid=(l+r)>>1;
		int alf=0;
		if(dfs(1,0,alf,mid)>=m)l=mid+1,ans=mid;
		else r=mid-1;
	}
	printf("%d",ans);
}

最后将各个模块拼起来就行qwq

原文地址:https://www.cnblogs.com/clockwhite/p/13451546.html