[ACM Day2]F.Floating Formation

题目描述

原题面等比赛结束后才能复制,现在只能大概用中文简述一下了。
给你 N N 只鸭子、M条使用中的船以及 K K 条未使用的船,M条船两边连接着两只不同的鸭子 i,j i , j ,每只鸭子只有当不少于2条船支撑时才不会沉下去。如果一旦一只鸭子沉没了,它所连带的船也会一起沉没,问如果用上 K K 条闲置的船,最少有多少只鸭子再也漂不起来。

数据范围

最多有100组数据,其中1<=N,M,K<=10000

分析

1.首先想一下,其实船不需要作为点连到整个图中,只需要将鸭子作为点连入即可(随便想一想就可以了)。
2.对于所有初始的状态,我们可以提前将所有应该沉下去的鸭子提前处理出来,只需要一遍 O(N) O ( N )
3.建立一个虚点 N+1 N + 1 连向所有沉没的鸭子,方便后面答案的统计,注意点 N+1 N + 1 的状态。
4.yy一个简单的贪心,对于每条闲置的船,如果把整个鸭子连成的图想象成一个类树一样的东西,我们每次需要浮起来的总是树上沉下去的最长链,所以我们可以把每个点所能拉起的最长链长度记下来,用priority_queue来维护,确保船的最大效用。
5.对于最长链,直接一遍dfs,更新节点深度的时候保证每次其深度都是由最深的节点转移而来,这样确保链最长。统计答案的时候还要注意一下可能存在的侧链,假如鸭子 u,v u , v 之间存在一条边,但 v v 不在u的最长链上,那么我们统计答案的时候把u,v切开,这样确保主链的完整。

#include <bits/stdc++.h>
#define rep( i , l , r ) for( int i = (l) ; i <= (r) ; ++i )
#define per( i , r , l ) for( int i = (r) ; i >= (l) ; --i )
#define erep( i , u ) for( int i = head[(u)] ; ~i ; i = e[i].nxt )
using namespace std;
int _read(){
    char ch = getchar();
    int x = 0 , f = 1 ;
    while( !isdigit( ch ) )
           if( ch == '-' ) f = -1 , ch = getchar();
           else ch = getchar();
    while( isdigit( ch ) )
           x = (ch  - '0') + x * 10 , ch =  getchar();
    return x * f;
}
const int maxn = 20000 + 5;
struct edge{
    int v , nxt;
} e[maxn * 3];
int head[maxn] , _t = 0;
inline void addedge( int u , int v ){
    e[_t].v = v , e[_t].nxt = head[u] , head[u] = _t++;
}
int D[maxn] , d[maxn];
bool vis[maxn] , alive[maxn];
void sink( int u ){
    if( d[u] > 1 || alive[u] == 0 ) return;
    alive[u] = 0;
    erep( i , u ){
        int v = e[i].v;
        d[v]--; sink( v );
    }
}
int dep[maxn];
inline int _max( int a , int b ){ return a > b ? a : b; }
void dfs( int u , int fa ){
    dep[u] = 1;
    erep( i , u ){
        int v = e[i].v;
        if( v == fa || alive[v]) continue;
        dfs( v , u );
        dep[u] = _max( dep[u] , dep[v] + 1 );
    }
}
int cnt[maxn];
void calc( int u , int fa ){
    bool flg = 1;
    erep( i , u ){
        int v = e[i].v;
        if( v == fa || alive[v] ) continue;
        if( dep[v] + 1 == dep[u] && flg ) cnt[v] = cnt[u] + 1 , flg = 0;
        else cnt[v] = 1;
        calc( v , u );
    }
} 
priority_queue<int> q;
int main(){
    int T = _read();
    while( T-- ){
        memset( D , 0 , sizeof D );
        memset( head , 0xff , sizeof head );
        _t = 0;
        int N = _read() , M = _read() , K = _read();
        int u , v;
        rep( i , 1 , M ){
            u = _read() , v = _read();
            addedge( u , v );
            addedge( v , u );
            D[u]++; D[v]++;
        }
        memcpy( d , D , sizeof D );
        rep( i , 1 , N ) alive[i] = 1;
        rep( i , 1 , N ) sink( i );
        alive[N + 1] = 0;
        int ans = N;
        rep( u , 1 , N )
            if( alive[u] ){
                erep( i , u )
                    if( !alive[e[i].v] ) addedge( N + 1 , e[i].v );
                ans--;
            }
        dfs( N + 1 , -1 );
        cnt[N + 1] = 0;
        calc( N + 1 , -1 );
        while( !q.empty() ) q.pop();
        rep( i , 1 , N )
            if( D[i] == 1 ) q.push( cnt[i] );
        rep( i , 1 , K ){
            if( q.empty() ) break;
            ans -= q.top(); q.pop();
        }
        cout << ans << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值