【NOIP 2018 提高组】赛道修建

传送门


problem

C 城将要举办一系列的赛车比赛。在比赛前,需要在城内修建 m m m 条赛道。

C 城一共有 n n n 个路口,这些路口编号为 1 , 2 , … , n 1,2,…,n 1,2,,n,有 n − 1 n-1 n1 条适合于修建赛道的双向通行的道路,每条道路连接着两个路口。其中,第 i i i 条道路连接的两个路口编号为 a i a_i ai b i b_i bi,该道路的长度为 l i l_i li。借助这 n − 1 n-1 n1 条道路,从任何一个路口出发都能到达其他所有的路口

一条赛道是一组互不相同的道路 e 1 , e 2 , … , e k e_1,e_2,…,e_k e1,e2,,ek,满足可以从某个路口出发,依次经过道路 e 1 , e 2 , … , e k e_1,e_2,…,e_k e1,e2,,ek每条道路经过一次,不允许调头)到达另一个路口。一条赛道的长度等于经过的各道路的长度之和。为保证安全,要求每条道路至多被一条赛道经过

目前赛道修建的方案尚未确定。你的任务是设计一种赛道修建的方案,使得修建的 m m m 条赛道中长度最小的赛道长度最大(即 m m m 条赛道中最短赛道的长度尽可能大)。

数据范围: 2 ≤ n ≤ 50000 2≤n≤50000 2n50000 1 ≤ m ≤ n − 1 1 ≤ m ≤ n-1 1mn1 1 ≤ l i ≤ 10000 1 ≤ l_i ≤ 10000 1li10000


solution

这道题二分应该很显然,如何检验答案?

考虑贪心。假设当前二分到的值为 k k k

下文中的 “ “ 匹配 ” ” 表示两条长度 < k <k <k 的链拼在一起后 ≥ k \geq k k

对于点 u u u,我们遍历它的儿子 v v v,每个 v v v 会传回来一个 v a l val val,表示 v v v 中匹配结束后剩下的最长的边,再加上 w ( u , v ) w(u,v) w(u,v)

那么当前合法的情况就两种:

  1. v a l ≥ k val\geq k valk
  2. v a l a + v a l b ≥ k val_a+val_b\geq k vala+valbk

对于第一种,直接 a n s + + ans++ ans++ 就可以。

对于第二种,我们就要匹配,我们按照 v a l val val 从小到大排序,对于每一个 v a l val val,找到第一个 ≥ k − v a l \geq k-val kval 的链,把它们拼到一起,然后 a n s + + ans++ ans++。这一步用 multiset 维护就行。

注意最后求一下直径作为二分的上界,防止被菊花图卡掉。


code

#include
#include
#include
#include
#define N 100005
using namespace std;
int n,m,t,cnt,l=1,r=0;
int first[N],v[N],w[N],nxt[N];
multiset<int>S;
multiset<int>::iterator it;
void add(int x,int y,int z){
	nxt[++t]=first[x],first[x]=t,v[t]=y,w[t]=z;
}
int f1[N],f2[N];
void dfs(int x,int fa){
	for(int i=first[x];i;i=nxt[i]){
		int to=v[i];
		if(to==fa)  continue;
		dfs(to,x);
		if(f1[x]<f1[to]+w[i]){
			f2[x]=f1[x];
			f1[x]=f1[to]+w[i];
		}
		else  if(f2[x]<f1[to]+w[i])
			f2[x]=f1[to]+w[i];
		r=max(r,f1[x]+f2[x]);
	}
}
int f[N],tmp[N];
void dp(int x,int fa,int k){
	for(int i=first[x];i;i=nxt[i])
		if(v[i]!=fa)  dp(v[i],x,k);
	int top=0;
	for(int i=first[x];i;i=nxt[i]){
		int to=v[i];
		if(to==fa)  continue;
		f[to]+=w[i];
		(f[to]>=k)?(++cnt):(tmp[++top]=f[to]);
	}
	sort(tmp+1,tmp+top+1),S.clear();
	for(int i=1;i<=top;++i){
		it=S.lower_bound(k-tmp[i]);
		if(it!=S.end())  S.erase(it),cnt++;
		else  S.insert(tmp[i]);
	}
	f[x]=S.size()?*S.rbegin():0;
}
bool check(int mid){
	cnt=0,dp(1,0,mid);
	return cnt>=m;
}
int main(){
	int x,y,z;
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;++i){
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z),add(y,x,z);
	}
	dfs(1,0);
	while(l<r){
		int mid=(l+r+1)>>1;
		if(check(mid))  l=mid;
		else  r=mid-1;
	}
	printf("%d\n",l);
	return 0;
}

你可能感兴趣的:(#,二分,#,贪心)