题目
点这里看题目。
分析
我们首先来研究一下一种后缀数组的出现条件。
后缀数组自身是一个 \(1\) 到 \(n\) 的排列 \(p\)。如果有字符串 \(S\) 的后缀数组是 \(p\),则我们可以考虑排序后相邻的两个后缀的比较:首先比较 \(S[p_k]\) 和 \(S[p_{k+1}]\),如果相同再比较 \(\operatorname{Suf}(S,p_k+1)\) 和 \(\operatorname{Suf}(S,p_{k+1}+1)\)。为了保持良定义,我们令 \(S[n+1]\) 为一个充分小的字符,这也相当于是“默认” \(p_0=n+1\)。
Remark.
可能用到了一点 SA-IS 排序的思想。SA-IS 里面对后缀分类时的技术与这里的手法有相似之处。
当然也有可能仅仅是我想不到而已。算是见识了一下这样的结论,也好。
由这个条件反推,由于 \(\operatorname{Suf}(S,p_k+1)\) 和 \(\operatorname{Suf}(S,p_{k+1}+1)\) 的大小关系也可以在 \(p\) 里面找出来,所以如果 \(p_k+1\) 排在 \(p_{k+1}+1\) 后面,则 \(S[p_k]\) 必须严格小于 \(S[p_{k+1}]\),反之 \(S[p_k]\) 只需要 \(\le S[p_{k+1}]\) 即可。也就是说,通过 \(p\) 我们可以生成一个关于 \(S\) 各字符的不等式链。
反过来,可以证明,如果 \(S\) 满足不等式链,则 \(p\) 的确是 \(S\) 的后缀数组,这样我们得到了:\(p\) 是 \(S\) 的后缀数组当且仅当 \(S\) 满足 \(p\) 生成的不等式链。
现在我们很容易对于一个后缀数组,检查它的存在性:先生成不等式链,然后从前往后依次从小到大放字符,遇到了 \(<\) 或者当前字符用完了就切换成下一种字符;最后只要能够将不等式链放满,我们就找到了一个对应的字符串。这相当于给出了一个 \(O(n\times n!)\) 的算法。
注意到,真正决定存在性的是不等式链而不是 \(p\) 本身。(不同的 \(p\) 可能生成相同的不等式链,但是 \(S\) 中字符排列顺序不同)所以我们从不等式链出发去计算相应的 \(p\) 的数量。
从不等式链怎样生成后缀数组?我们引入字符串这个媒介。不等式列到字符串,相当于是将不等式列的每个元素对应到原字符串上的位置。考虑一种最特殊的情况——所有 \(\le\) 两侧的字符都相同。此时字符串的方案数容易计算——假如 \(<\) 分割的段元素个数分别为 \(a_1,a_2,\dots,a_k\),则字符串方案数为 \(\dbinom{n}{a_1,a_2,\dots,a_k}\)。可以发现此时任意两个字符串对应的后缀数组都不相同。但是我们的工作还没有完成,这样的构造只保证了 \(\le\) 会在后缀数组生成的不等式链中保持,没有保证 \(<\) 也会保持。不过这个也暗示我们,这种算法仅仅会导致 \(<\) 退化成 \(\le\),施加容斥原理即可计算出保证 \(<\) 均保持的方案数。
Note.
容斥原理的正确性来自于,如果某个后缀数组存在 \(c\) 个位置的 \(<\) 退化成了 \(\le\),则它的计算系数为 \(\sum_{k=0}^c(-1)^k\binom{c}{k}=[c=0]\)。隐式的二项式反演。
实际面临的情况要复杂一点,因为将不等式链根据 \(<\) 剖分后每一个 \(\le\) 段的字符可能是不同的。不过,后缀数组生成的不等式链和字符串本身没有必然联系。如果我们遇到了一个“杂字符”的 \(\le\) 段,我们大可以将内部的字符看成完全相同的再代入上面讨论的情况,最后做一个字符的替换即可。替换后的字符串保持了原先的字符顺序,所以生成的后缀数组和原字符串生成的也是一样的。
Note.
注意这里的朴素替换手法的应用。主要是便于理解,
思考过程可能直接感性理解跳了。
现在,问题来到了“如何枚举不等式链”以及“如何快速容斥”。容易发现,两个过程其实可以合在一起做,因为划分不等式链的过程和容斥的过程在流程上类似。考虑这样的一个 DP:设 \(f_{i,j,k}\) 表示考虑前 \(i\) 个字符后,不等式链总长度为 \(j\),最后一个 \(\le\) 段长度为 \(k\) 的容斥系数和。转移考虑三种情况:
-
当前段加入一些字符后结束:\(f_{i,j+l,0}\leftarrow f_{i,j,k}\times \frac{1}{(k+l)!},1\le l\le c_i\)。
-
当前段加入一些字符后结束,但是进行了容斥:\(f_{i,j+l,k+l}\leftarrow -f_{i,j,k},1\le l\le c_i\)。
-
贪心地放置字符的过程中,当前字符用完了,切换到下一种字符:\(f_{i,j+c_i,k+c_i}\leftarrow f_{i,j,k}\)。
所有的转移都是对角线求和的形式,前缀和优化即可。复杂度为 \(O(n^3)\)。
代码
#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 = 1e9 + 7;
const int MAXN = 505;
template<typename _T>
inline void Read( _T &x ) {
x = 0; char s = getchar(); bool f = false;
while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s ^ '0' ), s = getchar(); }
if( f ) x = -x;
}
template<typename _T>
inline void Write( _T x ) {
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) Write( x / 10 );
putchar( x % 10 + '0' );
}
template<typename _T>
inline _T Min( const _T &a, const _T &b ) {
return a < b ? a : b;
}
int dp[2][MAXN][MAXN];
int fac[MAXN], ifac[MAXN];
int C[MAXN];
int N, M;
inline int Qkpow( int, int );
inline int Inv( const int &a ) { return Qkpow( a, mod - 2 ); }
inline int Mul( int x, const int &v ) { return 1ll * x * v % mod; }
inline int Sub( int x, const int &v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, const int &v ) { return ( x += v ) >= mod ? x - mod : x; }
inline int& MulEq( int &x, const int &v ) { return x = 1ll * x * v % mod; }
inline int& SubEq( int &x, const int &v ) { return ( x -= v ) < 0 ? ( x += mod ) : x; }
inline int& AddEq( int &x, const int &v ) { return ( x += v ) >= mod ? ( x -= mod ) : x; }
inline int Qkpow( int base, int indx ) {
int ret = 1;
while( indx ) {
if( indx & 1 ) MulEq( ret, base );
MulEq( base, base ), indx >>= 1;
}
return ret;
}
int main() {
Read( N ), Read( M );
rep( i, 1, M ) {
Read( C[i] );
if( ! C[i] ) {
i --, M --;
continue;
}
}
if( M > N ) return puts( "0" ), 0;
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 pre = 1, nxt = 0, ans = 0;
dp[nxt][0][0] = 1;
rep( i, 1, M ) {
pre ^= 1, nxt ^= 1;
rep( j, 0, N ) rep( k, 0, j ) {
dp[nxt][j][k] = 0;
if( j >= C[i] && k >= C[i] )
AddEq( dp[nxt][j][k], dp[pre][j - C[i]][k - C[i]] );
}
rep( j, 1, N ) rep( k, 1, j ) {
AddEq( dp[pre][j][k], dp[pre][j - 1][k - 1] );
int spn = Min( Min( j + 1, k + 1 ), C[i] + 1 );
AddEq( dp[nxt][j][0], Mul( Sub( dp[pre][j - 1][k - 1], spn > j || spn > k ? 0 : dp[pre][j - spn][k - spn] ), ifac[k] ) );
if( j ^ N )
SubEq( dp[nxt][j][k], Sub( dp[pre][j - 1][k - 1], spn > j || spn > k ? 0 : dp[pre][j - spn][k - spn] ) );
}
AddEq( ans, dp[nxt][N][0] );
}
Write( Mul( ans, fac[N] ) ), putchar( '\n' );
return 0;
}