JZOJ5000. 保镖

题目大意

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 + 10
typedef 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值