[LOJ2952]-[NOIP2018]赛道修建-树dp

说在前面

在ruc自习的第一天(此处应当@Narcissus)
原本在图书馆订了房,今天突然被告知图书馆放假了
不仅进不去,还要因为没有按时报道扣信用,神操作
(后来官方发了通知,扣的信用会在周一清掉)

感觉大家的生活都好颓废
希望开学会好起来


题目

LOJ2952传送门
看题戳传送门


解法

部分分给足了提示
最小值最大显然二分答案
对于一条链的情况,直接按mid切就好
菊花图的情况,大于mid的边直接计入答案,小于mid的两两匹配,要匹配数最大。不难发现,小配大是最优策略。

一棵树的情况,就是把以上两种合起来,并且把没有匹配到的最长的链向上传,用个map可以轻松实现
详见代码


下面是自带大长度的代码

#include <map>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int N , M , head[50005] , tp ;
struct Path{
	int pre , to , len ;
}p[100005] ;

void In( int t1 , int t2 , int t3 ){
	p[++tp] = ( Path ){ head[t1] , t2 , t3 } , head[t1] = tp ;
	p[++tp] = ( Path ){ head[t2] , t1 , t3 } , head[t2] = tp ;
}

int f[50005] , g[50005] , limit , sta[50005] , topp ;
map<int,int> cnt ;

int get_leval( int x ){
	int L = 1 , R = topp , mid , rt = 0x3f3f3f3f ;
	while( L <= R ){
		mid = ( L + R ) >> 1 ;
		if( sta[mid] < x ) L = mid + 1 ;
		else rt = sta[mid] , R = mid - 1 ;
	} return rt ;
}

void dfs( int u , int fa ){
	f[u] = 0 , g[u] = 0 ;
	for( int i = head[u] ; i ; i = p[i].pre ){
		int v = p[i].to ;
		if( v == fa ) continue ;
		dfs( v , u ) , f[u] += f[v] ;
	}

	int siz = 0 , now , tmp = 0 ;
	topp = 0 , cnt.clear() ;
	for( int i = head[u] ; i ; i = p[i].pre ){
		int v = p[i].to , len = p[i].len + g[v] ;
		if( v == fa ) continue ;
		if( len >= limit ){
			f[u] ++ ; continue ;
		} else if( cnt.count( len ) == 0 ){
			cnt[len] = 1 , sta[++topp] = len ;
		} else cnt[len] ++ ;
		siz ++ ;
	} sort( sta + 1 , sta + topp + 1 ) ;
	
	map<int,int>::iterator Lpt , Rpt ;
	while( siz > 1 ){
		Lpt = cnt.begin() , now = Lpt->first ;
		if( ( -- Lpt->second ) == 0 ) cnt.erase( Lpt ) ;
		siz -- ;
		Rpt = cnt.lower_bound( get_leval( limit - Lpt->first ) ) ;
		if( Rpt == cnt.end() ){
			tmp = now ;
			continue ;
		} if( ( -- Rpt->second ) == 0 ) cnt.erase( Rpt ) ;
		siz -- , f[u] ++ ;
	} g[u] = ( siz == 1 ? cnt.begin()->first : tmp ) ;
}

void solve(){
	int L = 0 , R = 49999 * 10000 , ans ;
	while( L <= R ){
		limit = ( L + R ) >> 1 ;
		dfs( 1 , 1 ) ;
		if( f[1] >= M ) ans = limit , L = limit + 1 ;
		else R = limit - 1 ;
	} printf( "%d" , ans ) ;
}

int main(){
	freopen( "track.in" , "r" , stdin ) ;
	freopen( "track.out", "w" , stdout) ;
	scanf( "%d%d" , &N , &M ) ;
	for( int i = 1 , u , v , L ; i < N ; i ++ ){
		scanf( "%d%d%d" , &u , &v , &L ) ;
		In( u , v , L ) ;
	} solve() ;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值