$[\ Nowcoder\ Contest\ 165\ \#D\ ]\ $ 合法括号序列


\(\\\)

\(Description\)


键盘上有三个键,敲击效果分别是:

  • 在输出序列尾部添加一个左括号
  • 在输出序列尾部添加一个右括号
  • 删除输出序列尾部的第一个元素,若输出序列为空,则什么都不发生

求恰好按键\(N\)次,输出序列是一个合法的括号序列的方案数对\(P\)取模的值。

只要按键顺序或内容有一个位置不同就视为不同。

  • \(N\in [1,10^3]\)\(P\in [1,10^4]\),不保证\(P\)为质数。

\(\\\)

\(Solution\)


神仙出题人神仙解法......

  • 首先有一个结论,对于一个长度确定的序列,其输出的方案数是确定的,且与具体每一个位置的内容无关。

    • 为什么呢?因为每一个位置输出是确定的,所以最后操作序列只需要改成符合要求的即可。

    • 于是我们先考虑求出输出一个长度为\(i\)的序列的方案数,设\(f[i][j]\)表示一共按键\(i\)次,当前输出序列长度为\(k\)的方案数。转移就很自然,考虑是新加上一个还是删掉一个末尾的。因为序列确定,如果新加上的是输出序列,那么新加上的方案是唯一的,而删除就不需要确定末尾是什么了。注意退格键在输出序列为空时也可使用,有转移方程:

    \[ f[i][j]=(f[i-1][max(0,j-1)]+f[i-1][j+1]\times 2)\%mod \]

  • 然后就是考虑长度为\(i\)的合法序列方案数了,这不是\(Catalan\)吗!一个非质数打你脸上分解质因数太麻烦了,然后出题神仙就给出了神仙\(DP\)做法。设\(g[i][j]\)为当前输出序列长度为\(i\),强制所有右括号合法,有\(j\)个左括号不合法的方案数。那么就有自然简单易懂直接的转移方程:
    \[ g[i][j]=(g[i-1][j+1]+(j>0?g[i-1][j-1]:0))\%mod \]
    代表新放一个右括号抵消掉一个左括号或新放一个左括号。

    \[ ans=\sum_{i=0}^{\lfloor\frac N 2\rfloor} f[n][i\times2]\times g[i\times 2][0] \]

\(\\\)

\(Code\)


#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define R register
#define N 1010
using namespace std;
 
int n,mod,ans,f[N][N],g[N][N];
 
int main(){
  scanf("%d%d",&n,&mod);
  f[0][0]=1;
  for(R int i=1;i<=n;++i)
    for(R int j=0;j<=i;++j)
      f[i][j]=(f[i-1][j+1]+(j!=0?f[i-1][j-1]:0))%mod;
  g[0][0]=1;
  for(R int i=1;i<=n;++i)
    for(R int j=0;j<=i;++j)
      g[i][j]=(g[i-1][max(0,j-1)]+(g[i-1][j+1]<<1))%mod;
  for(R int i=0;i<=(n>>1);++i) (ans+=g[n][i<<1]*f[i<<1][0])%=mod;
  printf("%d\n",ans);
  return 0;
}

猜你喜欢

转载自www.cnblogs.com/SGCollin/p/9664687.html