Loading

题解—P3000 [USACO10DEC]Cow Calisthenics G

做这题之前最好学会 “树形 \(dp\) 求树的直径”这一前缀知识(虽然我会但是我还是没想出来)

几乎想到要求直径这道题也没什么问题了(这不是废话吗,为什么题面里给了“直径”二字我硬是没往直径那想)

solution

首先不难发现,这是一道树上Monthly Expense S ,需要二分答案是显然的,并且还要利用它是一棵树的优势。

证明二分可行性最简单的办法就是看指针移动后候选集合是否存在包含关系,若存在,则必定有单调性。

然后我们现在的主要问题是如何 \(check\)

类比求直径,我们记录 \(d[x]\) 作为从 \(x\) 的子节点到达 \(x\) 的最长路径。

然后正常求直径,唯一的问题就是如果我们遇到“如果加上这个儿子就超过直径限制”的时候,我们就不得不断一条边还满足要求了。

这里贪心地断掉最长的边是显然的,因为当前的直径越短,之后的空间就越大。

没必要排序,只需要对求直径稍微改造即可。

code

(放着么丑的代码真是对不起大家的眼睛啦)

#include <cstring>
#include <algorithm>
#include <cstdio>
#define mp make_pair
#define R register int
#define int long 
#define printf Ruusupuu = printf

int Ruusupuu ;

using namespace std ;
typedef long long L ;
typedef long double D ;
typedef unsigned long long G ;
typedef pair< int , int > PI ;
const int N = 2e5 + 10 ;

inline int read(){
	int w = 0 ; bool fg = 0 ; char ch = getchar() ;
	while( ch < '0' || ch > '9' ) fg |= ( ch == '-' ) , ch = getchar() ;
	while( ch >= '0' && ch <= '9' ) w = ( w << 1 ) + ( w << 3 ) + ( ch ^ '0' ) , ch = getchar() ;
	return fg ? -w : w ; 
}

int n , m , x , y , mid , res , now , d [N] ; 
int cnt = 1 , fr [N << 1] , to [N << 1] , net [N << 1] , head [N << 1] , fg [N << 1] ; 

#define add( f , t ) fr [++ cnt] = f , to [cnt] = t , net [cnt] = head [f] , head [f] = cnt 

void sc(){
	n = read() , m = read() ; memset( head , -1 , sizeof( head ) ) ;
	for( R i = 1 ; i < n ; i ++ ) x = read() , y = read() , add( x , y ) , add( y , x ) ;	
}
	
inline void check( int x ){
	R i = head [x] ;
	while( ~i && fg [i] ) i = net [i] ;
	
	for( ; ~i ; i = net [i] ){
		if( fg [i] ) continue ;
		fg [i] = fg [i ^ 1] = 1 ;
		int y = to [i] ;
		check( y ) ;		
		if( d [x] + d [y] + 1 > mid ) res ++ , d [x] = min( d [x] , d [y] + 1 ) ;  // 1是edge[i]
		else d [x] = max( d [x] , d [y] + 1 ) ; 
	} 
}

void work(){
	
	int lside = 1 , rside = n , ans = 0 ;
	
	while( lside <= rside ){
		mid = ( lside + rside ) >> 1 ;
		for( R i = 0 ; i < N ; i ++ ) d [i] = 0 ;
		for( R i = 0 ; i < 2 * N ; i ++ ) fg [i] = 0 ; 
		res = 0 , check( 1 ) ;
		if( res <= m ) ans = mid , rside = mid - 1 ;
		else lside = mid + 1 ;
	} printf( "%ld\n" , ans ) ; 
}

signed main(){	
	sc() ;
	work() ;
	return 0 ;
} 

posted @ 2021-07-04 15:06  Soresen  阅读(37)  评论(0)    收藏  举报