[BZOJ3546]-[ONTAK2010]Life of the Party-二分图必匹配点

说在前面

并没有什么想说的,但是要保持格式=w=


题目

BZOJ3546传送门

题目大意

给出一个二分图 {A},{B} { A } , { B } ,求出其中的最大匹配关键点
最大匹配关键点是:如果该点不存在,则最大匹配数减少
范围: |A|,|B|104 | A | , | B | ≤ 10 4 |Edge|105 | E d g e | ≤ 10 5

输入输出格式

输入格式:
第一行输入三个数 N,M,K N , M , K ,表示两个集合的大小以及边数
接下来 K K 行,每行两个整数 ai,bj,表示 A A 中的第 i 号点与 B B 中的第 j 号点相连

输出格式:
输出所有的关键点编号,一个编号占一行
先输出 A A 中的,再输出 B 中的
编号需按照升序输出


解法

一开始往网络流必割边方面想了…然后就GG了…
去查了题解,发现这玩意居然在 2015国家集训队论文 有讲过(虽然没有例题),长郡中学陈胤伯的《浅谈图的匹配算法及其应用》

大概思想就是,如果一个点可以被其他点替换掉,那么这个点就不是关键点
「被其他点替换掉」,那么去替换匹配点的,就是未匹配点
从每个未匹配点出发,向走增广路那样,凡是被经过的同侧点,都可以被替换

所以这题,直接 Dinic D i n i c 一遍,然后建图dfs即可
Dinic跑二分图的复杂度是 Θ(mn) Θ ( m n ) ,因此是可过的


下面是代码

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

int N , M , K , boy[10005] , girl[10005] , S , T , head[20005] , tp ;
struct Path{
    int pre , to , flow ;
} p[120005] ;

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

struct Graph{
    Path p[240005] ;//(100000+10000+10000)*2
    int head[20005] , tp , dis[20005] , que[20005] , fr , ba , Flow ;
    void init(){
        tp = 1 ;
        memset( head , 0 , sizeof( head ) ) ;
    }
    void In( int t1 , int t2 ){
        p[++tp] = ( Path ){ head[t1] , t2 , 1 } ; head[t1] = tp ;
        p[++tp] = ( Path ){ head[t2] , t1 , 0 } ; head[t2] = tp ;
    }
    bool BFS(){
        memset( dis , -1 , sizeof( dis ) ) , dis[S] = 0 ;
        fr = 1 , ba = 0 , que[++ba] = S ;
        while( ba >= fr ){
            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 ) 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 ;
            if( ( nowf = dfs( v , min( flow , p[i].flow ) ) ) ){
                flow -= nowf , rt += nowf ;
                p[i].flow -= nowf , p[i^1].flow += nowf ;
                if( !flow ) break ;
            }
        } if( flow ) dis[u] = -1 ;
        return rt ;
    }
    void Dinic(){
        while( BFS() ) Flow += dfs( S , 10005 ) ;
    //  printf( "check Flow :%d\n" , Flow ) ;
    }
} G ;

bool vis[20005] ;
void dfs( int u ){
    vis[u] = true ;
    for( int i = head[u] ; i ; i = p[i].pre )
        if( !vis[ p[i].to ] ) dfs( p[i].to ) ;
}

void solve(){
    for( int i = 1 ; i <= N ; i ++ ) G.In( S , boy[i] ) ;
    for( int i = 1 ; i <= M ; i ++ ) G.In( girl[i], T ) ;
    G.Dinic() ;

    for( int u = 1 ; u <= N ; u ++ )
        for( int i = G.head[u] ; i ; i = G.p[i].pre ){
            if( G.p[i].to == S ) continue ;
            if( !G.p[i].flow ) In( G.p[i].to , u ) ; //matched edge
            else In( u , G.p[i].to ) ;
    } for( int i = G.head[S] ; i ; i = G.p[i].pre )
        if( G.p[i].flow ) In( S , G.p[i].to ) ; // dismatch
    dfs( S ) ;
    for( int i = 1 ; i <= N ; i ++ )
        if( !vis[i] ) printf( "%d\n" , i ) ;

    memset( vis , 0 , sizeof( vis ) ) ;
    memset( head , 0 , sizeof( head ) ) , tp = 0 ;
    for( int u = 10001 ; u <= 10000 + M ; u ++ )
        for( int i = G.head[u] ; i ; i = G.p[i].pre ){
            if( G.p[i].to == T ) continue ;
            if( G.p[i].flow ) In( G.p[i].to , u ) ; // matched edge
            else In( u , G.p[i].to ) ;
    } for( int i = G.head[T] ; i ; i = G.p[i].pre )
        if( !G.p[i].flow ) In( T , G.p[i].to ) ; // dismatch
    dfs( T ) ;
    for( int i = 10001 ; i <= 10000 + M ; i ++ )
        if( !vis[i] ) printf( "%d\n" , i - 10000 ) ;
}

int main(){
    scanf( "%d%d%d" , &N , &M , &K ) ;
    for( int i = 1 ; i <= N ; i ++ ) boy[i] = i ;
    for( int i = 1 ; i <= M ; i ++ ) girl[i]= 10000 + i ;
    S = 20002 , T = 20001 , G.init() ;

    for( int i = 1 , u , v ; i <= K ; i ++ ){
        scanf( "%d%d" , &u , &v ) ;
        G.In( boy[u] , girl[v] ) ;
    } solve() ;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值