Something special
This is my first attempt to write an algorithm solution.
Strictly speaking, it is to record my own understanding formed after reading the classical solution.
原题
给定一个括号序列,要求尽可能少地添加若干括号使得括号序列变得合法,当添加完成后,会产生不同的添加结果,请问有多少种本质不同的添加结果。
两个结果是本质不同的是指存在某个位置一个结果是左括号,而另一个是右括号。
例如,对于括号序列 (((),只需要添加两个括号就能让其合法,有以下几种不同的添加结果:()()()、()(())、(())()、(()()) 和 ((()))。
输入格式
输入一行包含一个字符串 s
,表示给定的括号序列,序列中只有左括号和右括号。
输出格式
输出一个整数表示答案,答案可能很大,请输出答案除以 1000000007
(即 109+7
) 的余数。
数据范围
对于 40%的评测用例,|s|≤200。
对于所有评测用例,1≤|s|≤5000。
输入样例:((()
输出样例:5
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
const int mod = 1e9 + 7;
char s[5003];
int f[5003][5003];
int add[5003];
int ans;
int Work(int len)
{
int lcnt = 0, rcnt = 0, num = 0; // 未被匹配的左, 右括号数, 右括号编号
memset(f, 0, sizeof(f));
for(int i = 1; i <= len; i ++)
{
if(s[i] == '(')
lcnt ++;
else
{
rcnt ++;
num ++;
if(lcnt) rcnt --, lcnt --;
add[num] = rcnt; // 记录最少需要添加的左括号的数量, add是单调不减的(虽然这个性质没用)
}
}
for(int i = add[1]; i <= len; i ++) f[1][i] = 1;
/* n ^ 3转移
for(int i = 2; i <= num; i ++)
for(int j = add[i]; j <= len; j ++)
for(int k = 0; k <= j; k ++)
f[i][j] = (f[i][j] + f[i - 1][j - k]) % mod;
*/
for(int i = 2; i <= num; i ++)
{
for(int j = 0; j <= add[i]; j ++) f[i][add[i]] = (f[i][add[i]] + f[i - 1][j]) % mod;
for(int j = add[i] + 1; j <= len; j ++) f[i][j] = (f[i][j - 1] + f[i - 1][j]) % mod;
}
return f[num][rcnt]; //返回答案
}
int main()
{
scanf("%s", s + 1);
int len = strlen(s + 1);
ans = Work(len);
for(int i = 1; i <= len; i ++)// 镜像
if(s[i] == '(')
s[i] = ')';
else
s[i] = '(';
std:: reverse(s + 1, s + len + 1);//翻转
ans = 1LL * ans * Work(len) % mod;
printf("%d\n", ans);
return 0;
}
作者:zth
链接:https://www.acwing.com/solution/content/47526/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
理解
1.最终的方案数分解为添加左括号的方案数乘以添加右括号的方案数
2.总框架使用动态规划,状态f[i][j]
意为当前枚举到第i
个右括号,添加了j
个左括号的合法方案
3.第一次尝试的三层循环
for(int i = 2; i <= num; i ++)
for(int j = add[i]; j <= len; j ++)
for(int k = 0; k <= j; k ++)
f[i][j] = (f[i][j] + f[i - 1][j - k]) % mod;
第一层:i
从2
开始,因为上一行代码初始化已完成对f[1][...]
的赋值
for(int i = add[1]; i <= len; i ++) f[1][i] = 1;
第二层:j
从add[i]
开始,因为小于add[i]
是不合法的方案
第三层:从先前已完成赋值的f[i-1][...]
出发完成对f[i][...]
的赋值,转移式子 f[i][j] = (f[i][j] + f[i - 1][j - k]) % mod
的含义为所要添加的左括号数量被分为j-k
个和k
个,j-k
个左括号在f[i-1][j-k]
:第i-1
个右括号前取得,k
个左括号在(隐含):第i-1
个括号和第i
个括号之间取得。
4.从三层循环优化到两层循环的步骤
(1)具化转移式子:
f[i][j] =f[i-1][0]+f[i-1][1]+...+f[i-1][j]
f[i][j-1]=f[i-1][0]+f[i-1][1]+...+f[i-1][j-1]
合并得到:
f[i][j]=f[i][j-1]+f[i-1][j]
观察发现:
倘若使用合并后的式子,同三层循环一样j
从add[i]
开始遍历,f[i][j-1]
中j-1
的存在会出现不合法的方案情形。
故对于合并后的式子,采用j
从add[i]+1
开始遍历的方式。
for(int j = add[i] + 1; j <= len; j ++) f[i][j] = (f[i][j - 1] + f[i - 1][j]) % mod;
此时剩余j=add[i]
的赋值情况未被考虑,
采用三层循环原思路分开所要添加的左括号的数量,一部分在第i-1
个右括号前取得,另一部分在第i-1
个括号和第i
个括号之间取得。
for(int j = 0; j <= add[i]; j ++) f[i][add[i]] = (f[i][add[i]] + f[i - 1][j]) % mod;
至此,理解结束。