「UOJ748」机器人表演
题目
点这里看题目。
分析
从一开始就知道正确的思路,到最后都没有写成正确的算法。
给定一个字符串 \(T\),考虑怎么验证它能不能由 \(S\) 和另外一个括号串合并起来。
一个自然的做法是,写一个 DP:设 \(f_{i,j}\) 表示 \(T[1,i]\) 能否由 \(S[1,j]\) 和另一个括号串前缀拼起来。如果我们设 \(a_i\) 为 \(T\) 的前缀和(\(0\) 视作 \(+1\),\(1\) 视作 \(-1\)),\(b_j\) 为 \(S\) 的前缀和,则可以得到转移:
注意到,我们只需要维护 \(f_i\) 即可完成检查。因此我们可以做一个 DP 套 DP,复杂度为 \(O(2^tn(n+t)^2)\)(注意 \(f_i\) 只有靠后的一段是有效的)。
然而,DP 需要记录的状态还是太多了,我们最好能够舍弃状态记录直接检查。
尝试从 DP 检查变为贪心检查。贪心的思路比较纯粹,我们维护每一个 \(T[1,i]\) 前缀在“保证括号序列不会失配”的前提下,能够和 \(S\) 匹配的最大前缀。如果我们扫描完整个 \(T\) 之后,最大前缀长度为 \(|S|\) 且 \(a_{|T|}=b_{|S|}\),\(T\) 就是合法的,否则就是非法的。
假如我们已知 \(a_i\) 和 \(T[1,i]\) 匹配的最大长度 \(j\),考虑每次加入一个字符后的转移:
-
如果 \(j<|S|\) 且 \(T[i+1]=S[j+1]\):直接匹配,转移到 \(j+1\);
-
如果加入 \(0\) 或者 \(a_i>b_j\):我们可以将下一个字符加入到括号序列中,转移到 \(j\);
-
如果加入 \(1\) 且 \(a_i=b_j\):此时会出现失配的情况。
假如转移到的匹配前缀长度为 \(j'\),则必须满足 \(a_i>b_{j'}\)。我们自然可以想到,转移到的状态应该是 \(\max_{j'<j,b_{j'}<a_i}j'\)。
后续还需要证明合法性和最优性:回退的过程实际上是将 \(S[j'+1,j]\) 和已有的括号序列前缀合并。原先的 \(S[j'+1,j]\) 一定是一个合法括号前缀,两个括号序列前缀合并后肯定还是一个括号序列前缀,且合并后 \(a_i>b_{j'}\) 保证了我们还可以加一个 \(1\),合法性得证。最优性比较显然。
那么我们可以做 DP,设 \(g_{i,j,k}\) 表示 \(T[1,i]\) 和 \(S\) 的最大匹配前缀长度为 \(k\),\(a_i=j\) 的方案数,转移可以做到 \(O(n(n+t)^2)\)。
Note.
实质上是建立了一个自动机,状态由 \((a,j)\) 确定,接收状态有且仅有 \((a_{|S|},|S|)\)。
这样两侧限制的处理,我们通常都是以一侧为主,贪心地去满足,当另一侧限制马上要被打破的时候再调整策略,或者另一侧限制被打破后调整到合法状态。
上述贪心实际上就是以 \(S\) 的匹配为主,另一侧的括号匹配限制了 \(S\) 的匹配。优点在于,括号串合法不合法是很容易用数值检查的,但是 \(S\) 的匹配特异性很强。
如果像我一开始的思路那样,先贪心地产生括号串的话,就不得不在“\(S\) 将要匹配不上时”调整匹配策略,不容易和 DP 一起维护。不清楚“回退”的思路在这个条件下起不起效,我怀疑可能是没有作用的。
所以问题出在两点:
没有把握“主要矛盾”,主次把握得不好。
一般方法不熟悉,实际上我只想到了“在限制将要打破时调整匹配策略”,而没有想到“回退”的想法。思路上的回退也有问题,很容易陷到怪圈里去。
代码
#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 MAXN = 305;
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' );
}
int pref[MAXN], jmp[MAXN];
int f[2][MAXN][MAXN << 2];
char str[MAXN];
int N, T;
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( T ), scanf( "%s", str + 1 );
rep( i, 1, N )
pref[i] = pref[i - 1] + ( str[i] == '0' ? +1 : -1 );
rep( i, 0, N ) {
jmp[i] = -1;
per( k, i - 1, 0 )
if( pref[k] < pref[i] ) {
jmp[i] = k; break;
}
}
int pre = 1, nxt = 0, M = N + 2 * T;
f[0][0][M] = 1;
rep( i, 1, M ) {
pre ^= 1, nxt ^= 1;
rep( j, 0, N ) rep( k, - M, M ) {
if( ! f[pre][j][k + M] ) continue;
int cur = f[pre][j][k + M];
// choose '0'
if( j < N && str[j + 1] == '0' ) AddEq( f[nxt][j + 1][k + 1 + M], cur );
else AddEq( f[nxt][j][k + 1 + M], cur );
// choose '1'
if( j < N && str[j + 1] == '1' ) AddEq( f[nxt][j + 1][k - 1 + M], cur );
else if( k > pref[j] ) AddEq( f[nxt][j][k - 1 + M], cur );
else if( ~ jmp[j] ) AddEq( f[nxt][jmp[j]][k - 1 + M], cur );
f[pre][j][k + M] = 0;
}
}
Write( f[nxt][N][pref[N] + M] ), putchar( '\n' );
return 0;
}