[BZOJ1565]-tarjan(或拓排)+最小割

说在前面

这题的数据…如果luogu上的数据就是当年的官方数据,未免也太水了一点…
me在fix_dfs函数里误调用了dfs函数,居然还能得80分???
(这个小错误me肉眼始终没看出来,然后随便造了一组数据me就WA了…有意思)


题目

BZOJ1565传送门
这题,不是很难,而且概括了题面就没什么意思了233
看题可戳传送门








解法

把样例的依赖关系画出来,立刻就可以知道这个题的做法了
这里写图片描述
可以发现这是一个,要想选当前格子,就必须先把指向它的格子先选中,把箭头反过来这就是一个闭合子图嘛,然后就是最大权闭合子图了。当然还没完,如果图里面出现了一个环,说明僵尸无论先进哪个格子都会死,那么这个环都不能选,并且这个环指向的格子也全都不能选。于是要给这些不能选的格子打上flag,这一步用tarjan和toposort都可以做到。
这道题就做完了


下面是自带大常数的代码

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

const int inf = 0x3f3f3f3f ;
bool flag[605] ;
int N , M , stp , shead[605] , score[605] , ind[605] ;
int tp = 1 , head[605] , maxn , S , T ;
struct Path{
    int pre , to , flow ;
}sp[360005] , p[360005] ;

int code( int i , int j ){ return i * M + j + 1 ; }
pair<int,int> decode( int x ){ return make_pair( ( x-1 )/M , ( x-1 )%M ) ;}

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

int sta[605] , topp ;
int dfn[605] , dfs_c , scc[605] , scc_cnt ;
int dfs( int u ){
    sta[++topp] = u ;
    int lowu = dfn[u] = ++dfs_c ;
    for( int i = shead[u] ; i ; i = sp[i].pre ){
        int v = sp[i].to ;
        if( !dfn[v] ) lowu = min( lowu , dfs( v ) ) ;
        else if( !scc[v] ) lowu = min( lowu , dfn[v] ) ;
    }
    if( lowu == dfn[u] ){
        scc_cnt ++ ;
        int tmp = topp ;
        while( 1 ){
            int x = sta[topp--] ;
            scc[x] = scc_cnt ;
            if( x == u ) break ;
        }
        if( tmp - topp > 1 )
        for( int i = topp + 1 ; i <= tmp ; i ++ )
            flag[ sta[i] ] = true ;
    } return lowu ;
}

void fix_dfs( int u ){
    flag[u] = true ;
    for( int i = shead[u] ; i ; i = sp[i].pre )
        if( !flag[ sp[i].to ] ) fix_dfs( sp[i].to ) ;
}

int dis[605] , que[605] , fr , ba ;
bool BFS(){
    memset( dis , -1 , sizeof( dis ) ) ;
    fr = 1 , ba = 0 ;
    dis[S] = 0 ; que[++ba] = S ;
    while( fr <= ba ){
        int u = que[fr++] ;
        for( int i = head[u] ; i ; i = p[i].pre ){
            int v = p[i].to ;
            if( dis[v] != -1 || !p[i].flow ) continue ;
            dis[v] = dis[u] + 1 ;
            que[++ba] = v ;
        }
    } return dis[T] != -1 ;
}

int dfs( int u , int flow ){
    if( u == T || !flow ) return flow ;
    int rt = 0 ;
    for( int i = head[u] ; i ; i = p[i].pre ){
        int v = p[i].to , nowf ;
        if( dis[v] != dis[u] + 1 || !p[i].flow ) continue ;
        nowf = dfs( v , min( flow , p[i].flow ) ) ;
        if( nowf ){
            rt += nowf ;
            flow -= nowf ;
            p[i].flow -= nowf ;
            p[i^1].flow += nowf ;
            if( !flow ) break ;
        }
    } if( !rt ) dis[u] = -1 ;
    return rt ;
}

void solve(){
    S = maxn + 1 , T = S + 1 ;
    for( int i = maxn ; i ; i -- )
        if( !dfn[i] ) dfs( i ) ;
    for( int i = maxn ; i ; i -- )
        if( flag[i] ) fix_dfs( i ) ;
    int ans = 0 ;
    for( int u = 1 ; u <= maxn ; u ++ ){
        if( flag[u] ) continue ;
        if( score[u] > 0 ){
            ans += score[u] ;
            In( S , u , score[u] ) ;
            In( u , S , 0 ) ;
        } else {
            In( u , T , -score[u] ) ;
            In( T , u , 0 ) ;
        } for( int i = shead[u] ; i ; i = sp[i].pre ){
            int v = sp[i].to ;
            if( !flag[v] ){
                In( v , u , inf ) ;
                In( u , v , 0 ) ;
            }
        }
    }
    while( BFS() )
        ans -= dfs( S , inf ) ;
    printf( "%d" , ans ) ;
}

int main(){
    scanf( "%d%d" , &N , &M ) ; maxn = N * M ;
    for( int i = 0 ; i < N ; i ++ ){
        for( int j = 0 , w ; j < M ; j ++ ){
            int now = code( i , j ) ;
            scanf( "%d%d" , &score[now] , &w ) ;
            for( int k = 1 , li , co ; k <= w ; k ++ ){
                scanf( "%d%d" , &li , &co ) ;
                sIn( now , code( li , co ) ) ;
            }
            if( j ) sIn( now , now - 1 ) ;
        }
    }
    solve() ;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值