题目
点这里看题目。
分析
显然可以构建出二分图的模型:将菜田放在左部,将订单放在右部,那么成为 Cabbage Master 的条件就是存在一个右部点被覆盖完的完美匹配。
那么很容易想到使用 Hall 定理。我们可以枚举右部的一个点集,并且取出右部中每个点的邻接点的并集,检查邻接点的总量是否足够。
形式化地,设左部点点集为 (L),右部点点集为 (R),对于 (xin R),它的邻接点集为 (S_x)。设 (f(S)=sum_{xin S}A_x)。那么存在右部点被覆盖完的完美匹配等价于:
由于 (n) 很小,我们自然想到将枚举的集合放到左部。枚举 (L'),此时 (x) 被计入 (R') 的条件是 (S_xsubseteq L')。因此,可以设 (g(S)=sum_{xin R}[S_xsubseteq S]B_x),那么条件顺利地转化为了:
现在我们想要这个条件被破坏,不难算出最小吃掉的卷心菜数量 (X):
提醒一下,(g) 可以直接高维前缀和计算;同时,如果不存在 (xin R,S_xsubseteq S),那么 (g(S)) 实际上为 (-infty) 而非 (0)。因为根据最初的计算式,这样的 (S) 根本不会成为 (igcup_{xin R'}S_x),所以我们不可以去考虑它。
考虑计算吃卷心菜的方案数。如果 (X=0),那么有且仅有“不吃”一种方案,我们将这种情况判掉。当 (X>0) 的时候,由于有最小化 (X) 的前提,所以我们必须保证存在一个最小化 (f(L')-g(L')+1) 的 (L'),使得我们吃的卷心菜的菜田集合为 (L') 的子集;这样就可以保证 (L') 的条件会被破坏。
形式化地,我们设 (h(S)) 为吃掉的卷心菜的菜田集合恰好为 (S) 的方案数,设 (mathcal{F}=argmin{f(L')-g(L')}),那么答案就是:
而 (h(S)) 不难使用容斥计算:
这个也可以调用高维前缀和。我们最终得到了复杂度为 (O(n2^n)) 的算法。
小结:
- 注意 Hall 定理的形式,尤其是调换枚举的部的时候;由于右部将邻接点取并,所以左部 (g(S)) 才会计算 (S_xsubseteq S);
- 注意 (g) 的一些细节。如果有困惑,从初始的正确式子入手逐步理清楚。
代码
#include <cstdio>
#include <iostream>
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
const int mod = 998244353, INF = 1e9;
const int MAXN = 25, MAXS = ( 1 << 20 ) + 5, MAXM = 2e6 + 5;
template<typename _T>
void read( _T &x )
{
x = 0; char s = getchar(); int f = 1;
while( ! ( '0' <= s && s <= '9' ) ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}
int fac[MAXM], ifac[MAXM];
int f[MAXS], g[MAXS], h[MAXS];
int cnt[MAXS], cont[MAXS];
int A[MAXN], B[MAXM], adj[MAXM];
int N, M;
inline int Qkpow( int, int );
inline int Mul( int x, int v ) { return 1ll * x * v % mod; }
inline int Inv( const int a ) { return Qkpow( a, mod - 2 ); }
inline int Sub( int x, int v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, int v ) { return ( x += v ) >= mod ? x - mod : x; }
inline int C( int n, int m ) { return n < m ? 0 : Mul( fac[n], Mul( ifac[m], ifac[n - m] ) ); }
inline int Qkpow( int base, int indx )
{
int ret = 1;
while( indx )
{
if( indx & 1 ) ret = Mul( ret, base );
base = Mul( base, base ), indx >>= 1;
}
return ret;
}
inline void Init( const int n = 2e6 )
{
fac[0] = 1; rep( i, 1, n ) fac[i] = Mul( fac[i - 1], i );
ifac[n] = Inv( fac[n] ); per( i, n - 1, 0 ) ifac[i] = Mul( ifac[i + 1], i + 1 );
}
int main()
{
Init();
read( N ), read( M );
rep( i, 0, N - 1 ) read( A[i] ), f[1 << i] = A[i];
rep( i, 0, M - 1 ) read( B[i] );
rep( i, 0, N - 1 ) rep( j, 0, M - 1 )
{
int c; read( c );
if( c ) adj[j] |= 1 << i;
}
rep( i, 0, M - 1 ) g[adj[i]] += B[i];
for( int S = 1 ; S < ( 1 << N ) ; S ++ )
cnt[S] = cnt[S >> 1] + ( S & 1 );
for( int S = 0 ; S < ( 1 << N ) ; S ++ )
f[S] = f[S & ( - S )] + f[S & ( S - 1 )];
for( int i = 0 ; i < N ; i ++ )
for( int S = 0 ; S < ( 1 << N ) ; S ++ )
if( S >> i & 1 ) g[S] += g[S ^ ( 1 << i )];
for( int S = 0 ; S < ( 1 << N ) ; S ++ )
if( ! g[S] ) g[S] = - INF;
int X = 1e9;
for( int S = 1 ; S < ( 1 << N ) ; S ++ )
X = std :: min( X, f[S] - g[S] + 1 );
X = std :: max( X, 0 );
if( X == 0 ) return puts( "0 1
" ), 0;
for( int S = 1 ; S < ( 1 << N ) ; S ++ )
{
if( f[S] - g[S] + 1 == X ) cont[S] = 1;
int tmp = C( f[S], X );
if( ! tmp ) continue;
h[S] = cnt[S] & 1 ? mod - tmp : tmp;
}
for( int i = 0 ; i < N ; i ++ )
for( int S = 0 ; S < ( 1 << N ) ; S ++ )
{
if( ! ( S >> i & 1 ) ) continue;
cont[S ^ ( 1 << i )] += cont[S];
h[S] = Add( h[S], h[S ^ ( 1 << i )] );
}
int ans = 0;
for( int S = 0 ; S < ( 1 << N ) ; S ++ )
if( cont[S] ) ans = cnt[S] & 1 ? Sub( ans, h[S] ) : Add( ans, h[S] );
write( X ), putchar( ' ' ), write( ans ), putchar( '
' );
return 0;
}