JZOJ5000. 保镖

来源:互联网 发布:中俄翻译软件 编辑:程序博客网 时间:2024/04/28 13:18

题目大意

5000留念
现在有一副若干条边的二分图,左边有N个点 ,右边有M个点 ,每个点有权值。一个图的子图定义为他端点的子集。一个合法的子图满足以下两个条件:

  1. 选出的点权和大于等于限制t
  2. 并且可以从图中选出若干条边,使得二分图中每个点最多被一条边覆盖,而选出的点要恰好被一条边覆盖。

求有多少个满足条件的子图。

Data Constraint
n,m20

题解

这题要用到霍尔定理。


Hall定理

简单讲一下我个人的理解。
对于一个二分图具备完备匹配的充要条件是对于左边点的每一个子集,和子集内的点相连的点集大小大于等于子集大小。


假如说只选出一遍的点的子集,我们显然可以Hall定理判断。实际上两边如果都满足,那么这个结论还是成立的。
所以我们只需要分别找出两边合法的方案,然后two pointers扫一下就解决了。

时间复杂度:O(2nn)

SRC

#include<cstdio>#include<cstdlib>#include<cstring>#include<iostream>#include<algorithm>#include<vector>using namespace std ;#define N 40 + 10#define M 2000000 + 10typedef long long ll ;bool f[2][M] ;ll Rec1[M] , Rec2[M] ;int W[N] , G[2][N] , Num[M] ;int n , on , m , T , Size ;ll ans ;void link1( int u , int v ) {    G[0][u] |= 1 << (v - 1) ;}void link2( int u , int v ) {    G[1][u] |= 1 << (v - 1) ;}int Count( int s ) {    int ret = 0 ;    while ( s ) {        ret ++ ;        s -= s & (-s) ;    }    return ret ;}void Solve( int n , int t ) {    for (int s = 0 ; s < (1 << n) ; s ++ ) {        f[t][s] = 1 ;        int st = 0 ;        ll sum = 0 ;        for (int i = 1 ; i <= n ; i ++ ) {            if ( (s >> (i - 1)) & 1 ) {                int _s = s ^ (1 << (i - 1)) ;                if ( !f[t][_s] ) { f[t][s] = 0 ; break ; }                st |= G[t][i] ;                sum += W[t*on+i] ;            }        }        if ( Num[st] < Num[s] ) f[t][s] = 0 ;        if ( f[t][s] ) {            if ( t == 0 ) Rec1[++Rec1[0]] = sum ;            else Rec2[++Rec2[0]] = sum ;        }    }}int main() {    freopen( "guard.in" , "r" , stdin ) ;    freopen( "guard.out" , "w" , stdout ) ;    scanf( "%d%d" , &n , &m ) ;    for (int i = 1 ; i <= n ; i ++ ) {        scanf( "\n" ) ;        for (int j = 1 ; j <= m ; j ++ ) {            char c ;            scanf( "%c" , &c ) ;            if ( c == '1' ) link1( i , j ) , link2( j , i ) ;        }    }    for (int i = 1 ; i <= n ; i ++ ) scanf( "%d" , &W[i] ) ;    for (int i = 1 ; i <= m ; i ++ ) scanf( "%d" , &W[n+i] ) ;    scanf( "%d" , &T ) ;    for (int s = 0 ; s < (1 << max(n,m)) ; s ++ ) Num[s] = Count(s) ;    on = n ;    Solve( n , 0 ) ;    Solve( m , 1 ) ;    sort( Rec1 + 1 , Rec1 + Rec1[0] + 1 ) ;    sort( Rec2 + 1 , Rec2 + Rec2[0] + 1 ) ;    int j = Rec2[0] + 1 ;    for (int i = 1 ; i <= Rec1[0] ; i ++ ) {        while ( j > 1 && Rec2[j-1] + Rec1[i] >= T ) j -- ;        if ( Rec2[j] + Rec1[i] >= T ) ans += Rec2[0] - j + 1 ;    }    printf( "%lld\n" , ans ) ;    return 0 ;}

以上.

1 0
原创粉丝点击