• 「UOJ748」机器人表演


    题目

    点这里看题目。

    分析

    从一开始就知道正确的思路,到最后都没有写成正确的算法

    给定一个字符串 \(T\),考虑怎么验证它能不能由 \(S\) 和另外一个括号串合并起来。

    一个自然的做法是,写一个 DP:设 \(f_{i,j}\) 表示 \(T[1,i]\) 能否由 \(S[1,j]\) 和另一个括号串前缀拼起来。如果我们设 \(a_i\)\(T\) 的前缀和(\(0\) 视作 \(+1\)\(1\) 视作 \(-1\)),\(b_j\)\(S\) 的前缀和,则可以得到转移:

    \[\begin{aligned} f_{i+1,j+1}\leftarrow f_{i,j},&\text{ if } T[i+1]=S[j+1]\\ f_{i+1,j}\leftarrow f_{i,j},&\text{ if } a_{i+1}\ge b_j \end{aligned} \]

    注意到,我们只需要维护 \(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\),考虑每次加入一个字符后的转移:

    1. 如果 \(j<|S|\)\(T[i+1]=S[j+1]\):直接匹配,转移到 \(j+1\)

    2. 如果加入 \(0\) 或者 \(a_i>b_j\):我们可以将下一个字符加入到括号序列中,转移到 \(j\)

    3. 如果加入 \(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.

    1. 实质上是建立了一个自动机,状态由 \((a,j)\) 确定,接收状态有且仅有 \((a_{|S|},|S|)\)

    2. 这样两侧限制的处理,我们通常都是以一侧为主,贪心地去满足,当另一侧限制马上要被打破的时候再调整策略,或者另一侧限制被打破后调整到合法状态

      上述贪心实际上就是以 \(S\) 的匹配为主,另一侧的括号匹配限制了 \(S\) 的匹配。优点在于,括号串合法不合法是很容易用数值检查的,但是 \(S\) 的匹配特异性很强。

      如果像我一开始的思路那样,先贪心地产生括号串的话,就不得不在“\(S\) 将要匹配不上时”调整匹配策略,不容易和 DP 一起维护。不清楚“回退”的思路在这个条件下起不起效,我怀疑可能是没有作用的。

      所以问题出在两点:

      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 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;
    }
    
  • 相关阅读:
    45_ansible概述、ansible基础 、ad-hoc、批量配置管理
    44_自定义镜像及仓库、持久化存储 、 Docker网络架构
    43_Docker概述、部署Docker、Docker镜像、Docker基本命令
    42_KVM简介、 Virsh管理 、 自定义虚拟机、虚拟设备管理
    41_iptables防火墙 filter表控制 扩展匹配 nat表典型应用
    40_系统审计 服务安全 Linux安全之打补丁
    39_加密与解密 AIDE入侵检测系统 扫描与抓包
    38_Linux基本防护 用户切换与提权 SSH访问控制 SELinux安全 、SSH访问控制 SELinux安全
    hdu5530
    bzoj3456
  • 原文地址:https://www.cnblogs.com/crashed/p/16571942.html
Copyright © 2020-2023  润新知