「ABC216H」Random Robots

题目

点这里看题目。

分析

尝试构建一个分层图来描述机器人的行动。抛开初始点不谈,我们可以构造出一个 \(n+1\) 层,每层有若干个点的图,用 \((i,j)\) 表示第 \(i\) 层的第 \(j\) 个点。那么机器人的静止可以用有向边 \((i,j)\rightarrow (i+1,j)\) 表示;移动可以用 \((i,j)\rightarrow (i+1,j+1)\) 表示。重要的性质是,这是一个平面图

加入初始层,也即新建一层,共 \(K\) 个点,第 \(i\) 个点连向 \(x_i\),该图的平面图性质保持不变。这样,一种机器人的移动方案就是从初始层到第 \(n+1\) 层的 \(K\) 条路径组合,对于初始层的每个点 \(u\) 都有且仅有一条路径起点在 \(u\),而我们所需要的方案数则是路径两两没有点相交的方案数。

再次注意这是一个平面图,我们可以使用 LGV 引理计算答案。由于第 \(n+1\) 层大小不确定,我们可以枚举其任意一个子集,按顺序作为每条路径的终点。

当然,这样算还是太暴力了,直接将矩阵乘起来再算行列式复杂度无法接受。由于矩阵形式简单,如果确定了起点终点,方案数可以直接用组合数算出来,所以我们可以尝试设计一个 DP 替代行列式。设 \(f_{i,S}\) 表示考虑了 \([0,i]\) 的终点后,已经确定了起点在 \(S\) 中的路径的带行列式系数的和。转移枚举加入 \(S\) 中的元素即可。

这样的时间复杂度即为 \(O(2^kk(n+\max x))\)

小细节:由于 \(x\) 可以为 0,所以我们需要考虑终点为 0 的情况。写刷表的时候尤其注意。

小结:

  1. 注意将机器人移动转化为图,将机器人移动方案转化为路径组的方法

  2. 注意这种行列式的计算方式,相当于变相地枚举行列式的排列。

    这样做的合理性在于元素之间相互独立,易于计算,我们就可以跳过矩阵。

代码

#include <cstdio>

#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;
const int MAXK = 15, MAXN = 1005, MAXS = ( 1 << 10 ) + 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[MAXN << 1], ifac[MAXN << 1];

int dp[MAXS];
int coe[MAXS][MAXK];

int X[MAXK];
int K, N;

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 void Upt( int &x, const int v ) { x = Add( x, v ); }

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 = 2000 )/*{{{*/
{
    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()
{
    read( K ), read( N );
    rep( i, 1, K ) read( X[i] ), X[i] ++;
    Init(), dp[0] = 1; 
    int all = ( 1 << K ) - 1;
    rep( S, 0, all )
    {
        coe[S][K] = 1;
        per( i, K - 1, 1 )
        {
            coe[S][i] = coe[S][i + 1];
            if( S >> i & 1 ) coe[S][i] = mod - coe[S][i];
        }
    }
    int div2 = Qkpow( ( mod + 1 ) / 2, N );
    rep( i, 0, X[K] + N )
        per( S, all, 0 )
    {
        if( ! dp[S] ) continue;
        rep( j, 1, K )
        {
            if( i + 1 < X[j] || i + 1 - N > X[j] || ( S >> ( j - 1 ) & 1 ) ) continue;
            Upt( dp[S | ( 1 << ( j - 1 ) )], Mul( Mul( div2, dp[S] ), Mul( coe[S][j], C( N, i + 1 - X[j] ) ) ) );
        }
    }
    write( dp[all] ), putchar( '\n' );
    return 0;
}
posted @ 2021-08-30 10:27  crashed  阅读(74)  评论(0编辑  收藏  举报