cf629 C. Famil Door and Brackets
题意:
给定长为 m 的括号序列,在序列的左右两边各加一些括号,扩展成长为 n 的合法括号序列,求方案数。
\(1\le m\le n\le 1e5,n-m\le2000\),注意 n 不一定是偶数
思路:
似乎可以嗯算,但是看到 n-m 只有 2000,所以当然是枚举+dp更方便啦!
众所周知,一个括号序列合法有两个条件:左括号总数等于右括号总数,且任意前缀中的左括号数大于等于右括号数。
枚举在左边共放 \(i\) 个 \((\) 和 \(j\) 个 \()\),则 \(i\ge j\)。对于给定串 \(s\) 的中每个位置 \(p\),应有 \(i+precnt_( [p] \ge j+precnt_) [p]\implies i-j\ge \max\limits_p \{ precnt_)-precnt_( \}\)
知道了左边放几个,可以推出右边放几个。从右往左看,应有 \(j'-i'\ge \max\limits_p \{ sufcnt_(-sufcnt_) \}\)
预处理上述 \(\max\)
dp预处理放 \(i\) 个 \((\)、\(j\) 个 \()\) 的方案数 \(f(i,j)\) 。\(f(i,j)=f(i-1,j)+f(i,j-1)\)
妈的说了这么多,其实就是个简单枚举
const signed N = 1e5 + 3, M = 2003, mod = 1e9 + 7;
ll n, m, f[M][M]; char str[N];
signed main() {
f[0][0] = 1; //dp预处理
for(int i = 0; i < M; i++)
for(int j = 0; j <= i; j++) {
if(i) f[i][j] += f[i-1][j];
if(j) f[i][j] += f[i][j-1];
f[i][j] %= mod;
}
iofast;
cin >> n >> m >> str + 1;
if(n % 2) return cout << 0, 0; //特判奇数
int lef = 0, rig = 0; //s中的左/右括号总数
for(int i = 1; i <= m; i++)
if(str[i] == '(') lef++; else rig++;
int llim = 0, rlim = 0; //算一下限制
for(int i = 1, s = 0; i <= m; i++) {
if(str[i] == ')') s++; else s--;
llim = max(llim, s);
}
for(int i = m, s = 0; i; i--) {
if(str[i] == '(') s++; else s--;
rlim = max(rlim, s);
}
ll ans = 0;
for(int i = 0; i + lef <= n/2; i++)
for(int j = 0; j + rig <= n/2 && i - j >= llim; j++) {
int ii = n/2 - i - lef, jj = n/2 - j - rig;
if(jj - ii >= rlim)
(ans += f[i][j] * f[jj][ii]) %= mod;
}
cout << ans;
}