「ABC215H」Cabbage Master

题目

点这里看题目。

分析

显然可以构建出二分图的模型:将菜田放在左部,将订单放在右部,那么成为 Cabbage Master 的条件就是存在一个右部点被覆盖完的完美匹配。

那么很容易想到使用 Hall 定理。我们可以枚举右部的一个点集,并且取出右部中每个点的邻接点的并集,检查邻接点的总量是否足够。

形式化地,设左部点点集为 (L),右部点点集为 (R),对于 (xin R),它的邻接点集为 (S_x)。设 (f(S)=sum_{xin S}A_x)。那么存在右部点被覆盖完的完美匹配等价于:

[forall R'subseteq R,fleft(igcup_{xin R'}S_x ight)ge sum_{xin R'}B_x\ Leftrightarrow min_{R'subseteq R}left{fleft(igcup_{xin R'}S_x ight)-sum_{xin R'}B_x ight}ge 0 ]

由于 (n) 很小,我们自然想到将枚举的集合放到左部。枚举 (L'),此时 (x) 被计入 (R') 的条件是 (S_xsubseteq L')。因此,可以设 (g(S)=sum_{xin R}[S_xsubseteq S]B_x),那么条件顺利地转化为了:

[min_{L'subseteq L}left{f(L')-g(L') ight}ge 0 ]

现在我们想要这个条件被破坏,不难算出最小吃掉的卷心菜数量 (X)

[X=maxleft{0,min_{L'subseteq L}{f(L')-g(L')+1} ight} ]

提醒一下,(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')}),那么答案就是:

[sum_{Ssubseteq L}[exist L'in mathcal F,Ssubseteq L']h(S) ]

(h(S)) 不难使用容斥计算:

[egin{aligned} inom{f(S)}{X}&=sum_{Tsubseteq S}h(T)\ Rightarrow h(T)&=sum_{Ssubseteq T}(-1)^{|T|-|S|}inom{f(S)}{X} end{aligned} ]

这个也可以调用高维前缀和。我们最终得到了复杂度为 (O(n2^n)) 的算法。

小结:

  1. 注意 Hall 定理的形式,尤其是调换枚举的部的时候;由于右部将邻接点取并,所以左部 (g(S)) 才会计算 (S_xsubseteq S)
  2. 注意 (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;
}
原文地址:https://www.cnblogs.com/crashed/p/15177582.html