「ABC215H」Cabbage Master

时间:2021-08-23
本文章向大家介绍「ABC215H」Cabbage Master,主要包括「ABC215H」Cabbage Master使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

题目

点这里看题目。

分析

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

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

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

\[\forall R'\subseteq R,f\left(\bigcup_{x\in R'}S_x\right)\ge \sum_{x\in R'}B_x\\ \Leftrightarrow \min_{R'\subseteq R}\left\{f\left(\bigcup_{x\in R'}S_x\right)-\sum_{x\in R'}B_x\right\}\ge 0 \]

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

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

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

\[X=\max\left\{0,\min_{L'\subseteq L}\{f(L')-g(L')+1\}\right\} \]

提醒一下,\(g\) 可以直接高维前缀和计算;同时,如果不存在 \(x\in R,S_x\subseteq S\)那么 \(g(S)\) 实际上为 \(-\infty\) 而非 \(0\)。因为根据最初的计算式,这样的 \(S\) 根本不会成为 \(\bigcup_{x\in R'}S_x\),所以我们不可以去考虑它。


考虑计算吃卷心菜的方案数。如果 \(X=0\),那么有且仅有“不吃”一种方案,我们将这种情况判掉。当 \(X>0\) 的时候,由于有最小化 \(X\) 的前提,所以我们必须保证存在一个最小化 \(f(L')-g(L')+1\)\(L'\),使得我们吃的卷心菜的菜田集合为 \(L'\) 的子集;这样就可以保证 \(L'\) 的条件会被破坏。

形式化地,我们设 \(h(S)\) 为吃掉的卷心菜的菜田集合恰好\(S\) 的方案数,设 \(\mathcal{F}=\arg\min\{f(L')-g(L')\}\),那么答案就是:

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

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

\[\begin{aligned} \binom{f(S)}{X}&=\sum_{T\subseteq S}h(T)\\ \Rightarrow h(T)&=\sum_{S\subseteq T}(-1)^{|T|-|S|}\binom{f(S)}{X} \end{aligned} \]

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

小结:

  1. 注意 Hall 定理的形式,尤其是调换枚举的部的时候;由于右部将邻接点取并,所以左部 \(g(S)\) 才会计算 \(S_x\subseteq 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\n" ), 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( '\n' );
    return 0;
}

原文地址:https://www.cnblogs.com/crashed/p/15177582.html