[NOIP2012]疫情控制

题目链接CodeVS1218

题目大意
大小为 N 的树上有M个可移动的军队,问至少需要多少时间,可以封锁这棵树(即从根节点开始不经过这些点驻扎的节点而到达叶子节点),军队在树上移动的时间即为树的边权。( N,M50000 )

分析
1. 对于所有的军队,越往上,显然能封锁的节点就越多;时间越多,能往上跑的距离就越多;所以二分时间。
2. 有一些军队,可以跨过根节点而控制根节点的其他子节点( 以下简称为子节点 )。
3. 对于不能跨根节点的军队( 包括不能到根和刚好能到根 ),直接在停止处标记;而那些可以跨根的军队,要记录起来( 记为 X )最后一起匹配。
4. 对于子节点,其是否需要从跟节点调用一支军队来控制可以通过一次dfs搜索判断;需要军队的记录下来( 记为 Y )。
5. 先把 X,Y 排序,对当前 X 的标记i和对当前 Y 的标记j不断后移。对于每一个 X[i] 有两种决策:控制自己的的来源或者控制另外的子节点。
6. 然而,当前的 X[i] 一定是剩余的军队中最没用的,所以如果它自己的子节点没有被控制,就去控制自己的节点,否则就去控制当先匹配到的 Y[j] (如果这个子节点已经被某个军队跑回来自己控制,那么就后移 j <script type="math/tex" id="MathJax-Element-14">j</script> 标记) 。
6.5 本人当时用了一个非常低效的匹配方法,导致无限T,因此开了一些优化,比如倍增(其实不倍增并不会慢多少)。

上代码

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

const int N = 5e4 + 10 ;
const int M = 30 + 10 ;

int n, m, tot ;
struct node_edge {
    int to, val ;
} ;
vector < node_edge > edge[ N ] ;

struct node_army { // 记录军队
    int plc, time, nown ; // 起始位置,剩余时限,目前位置
    // 其中plc是不变的,而time和nown是变化的
} army[ N ] ;

// dfs一棵树
// fa[i][j]表示i的 2^j 祖先,所以fa[i][0]即为i的父亲
int fa[ N ][ M ], dist[ N ] ; 
void dfs( int a , int b ) {
    fa[ a ][ 0 ] = b ;
    for ( int i = 0 ; i < edge[ a ].size() ; i ++ ) {
        int node = edge[ a ][ i ].to ;
        if ( node == b )    continue ;
        dist[ node ] = dist[ a ] + edge[ a ][ i ].val ;
        dfs( node , a ) ;
    }
}
// 为了优化,我连倍增都用了,当时无限T的心情可以脑补
// 树上倍增
inline void calc_fa() {
    for ( int j = 1 ; ; j ++ ) {
        int flag = false ;
        for ( int i = 1 ; i <= n ; i ++ ) {
            int node = fa[ i ][ j - 1 ] ;
            if ( !fa[ node ][ j - 1 ] ) continue ;
            flag = true ; fa[ i ][ j ] = fa[ node ][ j - 1 ] ;
        }
        if ( !flag )    return ;
    }
}
// 读入优化,说多了都是泪
inline void get_num( int &a ) {
    char ch ;
    while ( ch = getchar(), ch >= '0' && ch <= '9' )
        a = a * 10 + ch - '0' ;
    return ;
}
inline void init() {
    scanf( "%d", &n ) ; getchar() ;
    for ( int i = 1 ; i < n ; i ++ ) {
        int a = 0, b = 0, c = 0 ;
        get_num( a ), get_num( b ), get_num( c ) ;
        edge[ a ].push_back( (node_edge){ b , c } ) ;
        edge[ b ].push_back( (node_edge){ a , c } ) ;
        tot += c ; 
    }
    dfs( 1 , 0 ), calc_fa() ;
    scanf( "%d", &m ) ; getchar() ;
    for ( int i = 1 ; i <= m ; i ++ ) 
        get_num( army[ i ].plc ) ;
}

bool book[ N ] ; // 封锁标记
int lx, ly ;
struct node_xxyy {
    int from, val ;
    // 对X为军队的来源和剩余时间
    // 对Y为子节点和距离
    inline bool operator < ( const node_xxyy a ) const {
        return val < a.val ;
    }
} x[ N ], y[ N ] ;
inline void go_up( int a ) {
    int node = army[ a ].nown ;
    while ( fa[ node ][ 0 ] != 1 ) {
        int k = 0 ;
        while ( fa[ node ][ k ] != 1 && fa[ node ][ k ] != 0 ) {
            if ( dist[ node ] - dist[ fa[ node ][ k ] ] <= army[ a ].time )
                k ++ ;
            else    break ;
        }
        if ( k == 0 ) {
            book[ army[ a ].nown = node ] = true ; return ;
        } else {
            army[ a ].time -= dist[ node ] - dist[ fa[ node ][ k - 1 ] ] ;
            army[ a ].nown = node = fa[ node ][ k - 1 ] ;
        }
    }
    if ( army[ a ].time <= dist[ army[ a ].nown ] )
        book[ army[ a ].nown ] = true ;
    return ;
}
bool check( int a ) {
    if ( book[ a ] )    return true ;
    for ( int i = 0 ; i < edge[ a ].size() ; i ++ ) {
        int node = edge[ a ][ i ].to ;
        if ( node == fa[ a ][ 0 ] ) continue ;
        if ( !check( node ) )   return false ;
    }
    if ( edge[ a ].size() > 1 ) {
        book[ a ] = true ; return true ; //优化之一
    } else 
        return false ;
}

inline bool judge( int valn ) {
    for ( int i = 1 ; i <= m ; i ++ )
        army[ i ].time = valn, army[ i ].nown = army[ i ].plc ;
    for ( int i = 1 ; i <= m ; i ++ ) {
        go_up( i ) ;
        if ( army[ i ].time > dist[ army[ i ].nown ] ) // 说明可以越根
            x[ ++ lx ] = (node_xxyy){ army[ i ].nown , army[ i ].time - dist[ army[ i ].nown ] } ;
    }
    for ( int i = 0 ; i < edge[ 1 ].size() ; i ++ )
        if ( !check( edge[ 1 ][ i ].to ) )  
            y[ ++ ly ] = (node_xxyy){ edge[ 1 ][ i ].to , dist[ edge[ 1 ][ i ].to ] } ;
    if ( lx < ly )  return false ; // 剪枝
    sort( x + 1 , x + lx + 1 ) ; sort( y + 1 , y + ly + 1 ) ;
    int mark = 1 ;
    for ( int i = 1 ; i <= lx ; i ++ ) { //我T就是T在这个匹配,我原来的版本太low了:在Y中upper_bound()
        if ( !book[ x[ i ].from ] ) {
            book[ x[ i ].from ] = true ; continue ;
        } else {
            while ( book[ y[ mark ].from ] && mark < ly )   mark ++ ;
            if ( x[ i ].val >= y[ mark ].val ) {
                book[ y[ mark ].from ] = true ;
                mark ++ ;
            }
        }
    }
    for ( int i = 1 ; i <= ly ; i ++ )
        if ( !check( y[ i ].from ) )    return false ;
    return true ;
}
inline int figure() {
    if ( m < edge[ 1 ].size() ) return -1 ; // 唯一无法封锁的情况
    int ans ;
    int l = 1, r = tot, mid ;
    while ( l <= r ) {
        lx = ly = 0 ;
        memset( book , 0 , sizeof( book ) ) ;
        mid = ( l + r ) >> 1 ;
        if ( judge( mid ) )
            r = mid - 1, ans = mid ;
        else
            l = mid + 1 ;
    }
    return ans ;
}

int main() {
    init() ;
    printf( "%d\n", figure() ) ;
    return 0 ;
}

以上

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值