• 「CTSC2016」萨菲克斯·阿瑞


    题目

    点这里看题目。

    分析

    我们首先来研究一下一种后缀数组的出现条件。

    后缀数组自身是一个 \(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\) 的容斥系数和。转移考虑三种情况:

    1. 当前段加入一些字符后结束:\(f_{i,j+l,0}\leftarrow f_{i,j,k}\times \frac{1}{(k+l)!},1\le l\le c_i\)

    2. 当前段加入一些字符后结束,但是进行了容斥:\(f_{i,j+l,k+l}\leftarrow -f_{i,j,k},1\le l\le c_i\)

    3. 贪心地放置字符的过程中,当前字符用完了,切换到下一种字符:\(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;
    }
    
  • 相关阅读:
    SQLServer 可疑
    String与Long互转
    洛谷 P5644
    洛谷 P3783
    洛谷 P4663
    洛谷 P3438
    Atcoder Grand Contest 054 题解
    迭代器失效问题
    Solution -「CF 232E」Quick Tortoise
    Solution -「NOI 2020」「洛谷 P6776」超现实树
  • 原文地址:https://www.cnblogs.com/crashed/p/16555739.html
Copyright © 2020-2023  润新知