【AtCoder Regular Contest 059 F】Unhappy Hacking

动态规划。

题目大意:

给定 $n$ 和一个 01 串 $s$。你有一个初始为空的字符串 $t$,要对其进行 $n$ 次操作,每次操作有三种:

  • 在 $t$ 后面补上一个 0
  • 在 $t$ 后面补上一个 1
  • 删除 $t$ 末尾的一个字符。若 $t$ 为空串,则忽略这次操作

问有多少种不同的操作序列,使得最后 $t=s$。

题解:

由于 $t$ 要等于 $s$,所以 $t$ 中肯定存在一个加字符的子序列,其恰好为 $s$。而剩下的操作,就是补若干个字符,然后再删掉。

我们必须保证每一时刻,补上的字符必须不多于删掉的字符,这样才不会影响子序列上的元素。这就是一个括号匹配问题。$n$ 个左括号个 $n$ 个右括号的匹配方案恰好是卡特兰数的第 $n$ 项。可以使用 $\frac{\binom{2n}n}{n+1}$。由于这里的 01 是可以任意选的,所以方案数还需要乘上 $2^n$。

我们需要在子序列的间隙中把这些东西插进去。可以用 $dp_{i,j}$ 表示前 $i$ 个间隙中插入了 $j$ 对括号的方案。转移是个卷积的形式,直接做是 $O(n^3)$ 的。可以使用多项式技巧做到 $O(n\cdot\mathrm{poly}(\log n))$,但由于这题 $n$ 比较小,直接快速幂暴力卷积即可,时间复杂度 $O(n^2\log n)$。

还有一个问题,我们可以在串的开头插入一些对空串的“浪费”操作。这里为了保证不会算重,我们规定“浪费”操作序列的最末尾一定是对空串进行的删除操作。

我们用 $g_{i,j}$ 表示做了 $i$ 个操作,当前字符串长度为 $j$ 的方案数。

那么 $g_{i,j}=2g_{i-1,j-1}$($j\gt 0$,表示插入一个字符)。$g_{i,j}=g_{i-1,j+1}$(表示删除一个字符)。$g_{i,0}=g_{i-1,0}$(表示对空串删除一个字符)。

计算的时间复杂度为 $O(n^2)$。

我们查长度为 $k$ 的前缀的方案数,就是 $g_{k-1,0}$(前 $k-1$ 位要没有字符,最后钦定执行一次删除)。

我们枚举前面“浪费”的前缀长度,然后使用 $g$ 和 $dp$ 再进行组合就可以计算。

Code:

#include<cstdio>
#include<cstring>
typedef long long LL;
const int md=1e9+7,N=16384;
int fac[N],iv[N],n,c[5002],dp[5002],g[5002],h[5002],s[5002];
char v[N];
inline int C(int n,int m){return(LL)fac[n]*iv[m]%md*iv[n-m]%md;}
inline int pow(int a,int b){
    int ret=1;
    for(;b;b>>=1,a=(LL)a*a%md)if(b&1)ret=(LL)ret*a%md;
    return ret;
}
void PW(int b,int n){
    static int A[5002];
    *dp=1;
    for(;b;b>>=1){
        if(b&1){
            memset(A,0,sizeof A);
            for(int i=0;i<=n;++i)for(int j=0;j<=i;++j)
            A[i]=(A[i]+(LL)dp[j]*c[i-j])%md;
            memcpy(dp,A,sizeof A);
        }
        memset(A,0,sizeof A);
        for(int i=0;i<=n;++i)for(int j=0;j<=i;++j)
        A[i]=(A[i]+(LL)c[j]*c[i-j])%md;
        memcpy(c,A,sizeof A);
    }
}
int main(){
    scanf("%d%s",&n,v);
    int L=strlen(v),k=n-L>>1;
    for(int i=*fac=1;i<=2*n;++i)fac[i]=(LL)fac[i-1]*i%md;
    iv[2*n]=pow(fac[2*n],md-2);
    for(int i=2*n-1;~i;--i)iv[i]=(i+1LL)*iv[i+1]%md;
    for(int i=0,w=1;i<=k;++i,w=w*2>=md?w*2-md:w*2)
    c[i]=(LL)C(2*i,i)*iv[i+1]%md*fac[i]%md*w%md;
    PW(L+1,k);
    h[0]=g[0]=1;
    for(int i=1;i<=2*k+1;++i){
        memset(s,0,sizeof s);
        for(int j=1;j<=2*k+1;++j)s[j]=g[j-1]*2%md;
        for(int j=0;j<2*k+1;++j)s[j]=(s[j]+g[j+1])%md;
        s[0]=(s[0]+g[0])%md;
        h[i]=s[0];
        memcpy(g,s,sizeof s);
    }
    int ans=0;
    for(int i=0;i<=k;++i){
        int sy=n-2*i-L;
        int ch=(sy==0)?1:h[sy-1];
        ans=(ans+(LL)ch*dp[i])%md;
    }
    printf("%d\n",ans);
    return 0;
}