C++算法题括号序列——题解理解记录

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;

第一层:i2开始,因为上一行代码初始化已完成对f[1][...]的赋值

for(int i = add[1]; i <= len; i ++) f[1][i] = 1;

第二层:jadd[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]
观察发现:
倘若使用合并后的式子,同三层循环一样jadd[i]开始遍历,f[i][j-1]j-1的存在会出现不合法的方案情形。
故对于合并后的式子,采用jadd[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;

至此,理解结束。

Ending:

Thank you for your reading.

  • 11
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值