题解:CF2169F Subsequence Problem

· · 题解

一个自然的想法是,看看怎样的序列 c 是合法的。也就是说,我们取极限状态。

首先考察 a_1 中所有元素第一次出现的位置集合 S_1。那么显然最严的 b 序列取 S_1 的最大元素 r_1。这要求所有的 a_2 中的元素都至少在 r_1 后面出现一次;于是我们继续考察后缀 (r_1,n] 上所有 a_2 中元素的首次出现集合 S_2,同理取 S_2 中最大元素 r_2 不劣。这么一来重复这个过程,如果一直到 S_k 都能正常取得,那么 c 合法。

考虑一切合法 c 均可以用如下方式得到:选 l_1 个位置排列 a_1 的元素;然后在后方排列 l_2a_2 的元素,以此类推;记一个 a 序列的是一组。然后考虑所有空位 i,不难发现 i 上只要不放其所在位置所属的组(一个空位置 p 如果 p>r_kp<r_{k+1})则位置 p 属于组 k+1,最后的若干个空位不属于任何组)位于 i 后面的元素即可。

倘若我们已知 a 的排列,方案数是好计算的:每个空位的方案数均形如 m-k,其中 k 是同组被禁止放的数的个数,k\in[1,l_i](当空位位于最后方时 k=0)。但我们不知道 a 的排列,于是考察以下计算方法:

首先先不带空位,单独排列 a,这个的方案数是 \displaystyle\prod_{i=1}^kl_i!。那么实际上插入这些空位,方案数可以写作

看起来很不可做,但是注意到 $l_i\leq 5$,于是我们可以把所有相同的 $m-e$($e\in[0,5]$)放在一起,我们考虑单独插入 $5$ 次,第 $i$ 次插入所有 $e=i$ 的空位,那么记 $e$ 为常数,枚举 $C$ 为这样的空位的总数量,$c_e$ 为满足 $m-(l_i-j+1)=e$ 的 $(i,j)$ 数对个数,那么单次插入的方案数就是 $\displaystyle\left(\sum_{\sum_{i,j:l_i-j+1=e}L_{i,j}=C}(m-e)^{C}\right)\binom{C+c_e-1}{C}$,后面是个插板。 不难发现可以卷积。把上式记作 $\displaystyle [x^C]F_e(x)=(m-e)^C\binom{C+c_e-1}{C}$,整个题的答案就是 $\displaystyle\left(\prod_{i=1}^k(l_i!)\right)[x^{n-\sum l}]\prod_{e=0}^5F_e(x)$,NTT 可以做到 $O(nl\log n)$。 fun fact:jly 有很牛的 $O(nl)$ 做法,但我没看懂 XD。 ```cpp #include<bits/stdc++.h> using namespace std; #define int long long #define MAXN 2000005 #define mod 998244353 typedef vector<int> poly; int g,ig,gpow[25],igpow[25],rev[MAXN],fac[MAXN],inv[MAXN],ifac[MAXN]; inline int C( int n , int m ){ return n >= m ? fac[n] * ifac[m] % mod * ifac[n - m] % mod : 0; } inline int fp( int x , int p ){ int res = 1; while( p ){ if( p & 1 ) res = res * x % mod; x = x * x % mod; p >>= 1; } return res; } inline void polypre(){ g = 3,ig = fp( g , mod - 2 ); for( int i = 1 ; i <= 23 ; i ++ ) gpow[i] = fp( g , ( mod - 1 ) / ( 1 << i ) ); for( int i = 1 ; i <= 23 ; i ++ ) igpow[i] = fp( ig , ( mod - 1 ) / ( 1 << i ) ); } inline int redadd( int x ){ return x >= mod ? x - mod : x; } inline int reduce( int x ){ return x < 0 ? x + mod : x; } inline void NTT( vector<int> &F , int typ ){ int len = F.size(); for( int i = 0 ; i < len ; i ++ ) rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) * ( len >> 1 ) ); for( int i = 0 ; i < len ; i ++ ) if( i < rev[i] ) swap( F[i] , F[rev[i]] ); for( int i = 1 , t = 1 ; i < len ; i <<= 1 , t ++ ){ int step = typ == 1 ? gpow[t] : igpow[t]; for( int j = 0 ; j < len ; j += i << 1 ){ int omega = 1; for( int k = 0 ; k < i ; k ++ , omega = omega * step % mod ){ int X = F[j + k],Y = omega * F[i + j + k] % mod; F[j + k] = redadd( X + Y ),F[i + j + k] = reduce( X - Y ); } } } if( typ == -1 ) for( int i = 0 , ilen = fp( len , mod - 2 ) ; i < len ; i ++ ) F[i] = F[i] * ilen % mod; } inline poly polymul( poly F , poly G ){ int n = F.size(),m = G.size(),L = n + m - 1; int len = 1; while( len < L ) len <<= 1; F.resize( len ),G.resize( len ); NTT( F , 1 ),NTT( G , 1 ); for( int i = 0 ; i < len ; i ++ ) F[i] = F[i] * G[i] % mod; NTT( F , -1 ),F.resize( L ); return F; } int n,m,k,l[MAXN],c[6]; poly P; inline void solve(){ scanf("%lld%lld%lld",&n,&m,&k); for( int i = 1 ; i <= k ; i ++ ) scanf("%lld",&l[i]); for( int i = 1 ; i <= k ; i ++ ){ int x; for( int j = 1 ; j <= l[i] ; j ++ ) scanf("%lld",&x); } for( int e = 0 ; e <= 5 ; e ++ ) c[e] = 0; for( int i = 1 ; i <= k ; i ++ ) for( int j = l[i] - 1 ; j >= 0 ; j -- ) c[l[i] - j] ++; c[0] ++; poly A; A.resize( n , 0 ),A[0] = 1; for( int e = 0 ; e <= 5 ; e ++ ){ P.resize( n + 1 ); P[0] = 1; for( int i = 1 ; i <= n ; i ++ ) P[i] = P[i - 1] * ( m - e ) % mod; for( int i = 1 ; i <= n ; i ++ ) P[i] = P[i] * C( i + c[e] - 1 , i ) % mod; A = polymul( A , P ); A.resize( n + 2 ); } int sl = 0; for( int i = 1 ; i <= k ; i ++ ) sl += l[i]; int ans = A[n - sl]; for( int i = 1 ; i <= k ; i ++ ) ans = ans * fac[l[i]] % mod; printf("%lld\n",ans); } signed main(){ polypre(); fac[0] = inv[1] = ifac[0] = 1; for( int i = 1 ; i < MAXN ; i ++ ) fac[i] = fac[i - 1] * i % mod; for( int i = 2 ; i < MAXN ; i ++ ) inv[i] = ( mod - mod / i ) * inv[mod % i] % mod; for( int i = 1 ; i < MAXN ; i ++ ) ifac[i] = ifac[i - 1] * inv[i] % mod; int testcase = 1; while( testcase -- ) solve(); } ```