[NOIP2018]赛道修建(树形dp+二分)

弱鸡萌新2018年难忘的骗分之旅

花了2个小时骗分2333

题意

从n个点构成的的树中取出m条边不重复路径,使得最小的路径最长

思路

  1. 由于没有一个确定的限制且问题具有单调性,首先肯定想到二分答案,设该数为x,那么需要找出长度大于等于x的路径条数

  2. 对于一个子树i,考虑它的贡献,分为两种:① i子树中选一条或两条链,使它们拼起来大于等于x ,② 父亲节点通过连向i子树的这条边使用i子树中的一条链,这个贡献最多只能有一次(因为父亲到i的边只有一条)。那么把哪一条链贡献给父亲呢?使用贪心策略

  3. 贪心:让i子树自己内部能配对的尽量配对,达到最多的配对数之后,将未配对的最大链贡献给父亲即可。这样的正确性是显然的,因为如果i子树中有两条链本来可以配对却不让它们配对,那么路径数少1,把其中一条贡献给父亲,通过2中叙述可知,这样最多使路径数加1,但是会多浪费一些父亲子树中的一些边,肯定不如直接配对。

  4. 在使配对数最多的情况下,由于还要把未配对最大链贡献给父亲,所以这时候可以二分最大的链,检验如果没有用这条链,是否还能达到最大匹配数,如果能则说明可以不用这条链,即可以贡献给父亲

实现

上述思路总结起来的实现就是: 二分+排序+二分

排序用vector或者multiset即可

Code:

#include<bits/stdc++.h>
#define N 50005
using namespace std;
const int INF = 2000000000;
int n,m,limit,sum;
vector <int> e[N];

struct Edge
{
	int next,to,dis;
}edge[N<<1];int head[N],cnt=1;
void add_edge(int from,int to,int dis)
{
	edge[++cnt].next=head[from];
	edge[cnt].to=to;
	edge[cnt].dis=dis;
	head[from]=cnt;
}
template <class T>
void read(T &x)
{
	char c;int sign=1;
	while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
	while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
}
bool ck(int rt,int pos,int l,int r,int goal)//不用pos是否能凑出goal个 
{
	int tot=0;
	while(l<r)
	{
		if(r==pos) --r;
		if(l==pos) ++l;
		while((e[rt][l]+e[rt][r]<limit||l==pos)&&(l<r)) ++l;
		if(!(l<r)) break;
		++tot;++l;--r;
	}
	return (tot>=goal);
}
int dfs(int rt,int fa)
{
	for(int i=head[rt];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==fa) continue;
		e[rt].push_back(edge[i].dis+dfs(v,rt));
	}
	if(e[rt].empty()) return 0; 
	sort(e[rt].begin(),e[rt].end());
	int l=0,r=(int)e[rt].size()-1;
	while(e[rt][r]>=limit) ++sum,--r;//单独成链 
	int rest=r+1,tot=0;
	while(l<r)
	{
		while(e[rt][l]+e[rt][r]<limit&&(l<r)) ++l;
		if(!(l<r)) break;
		++tot;++l;--r;
	}
	sum+=tot;
	if(tot*2==rest) return 0;//完全配对 
	//否则一定剩下至少一个 
	l=0;r=rest-1;int ret=0;
	while(l<=r)//二分出返回值 
	{
		int mid=(l+r)>>1;
		if(ck(rt,mid,0,rest-1,tot)) l=mid+1,ret=max(mid,ret);
		else r=mid-1;
	}
	return e[rt][ret];
}
bool check(int x)//求所有大于等于x长度的赛道 
{
	for(int i=1;i<=n;++i) e[i].clear();
	limit=x;sum=0;
	dfs(1,0);
	return (sum>=m);
}
int main()
{
	read(n);read(m);
	for(int i=1;i<n;++i)
	{
		int x,y,z;
		read(x);read(y);read(z);
		add_edge(x,y,z);
		add_edge(y,x,z);
	}
	int l=0,r=INF,ans=-INF;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid)) l=mid+1,ans=max(ans,mid);
		else r=mid-1;
	}
	printf("%d
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/Chtholly/p/11234691.html