[01背包] Pieces of Parentheses (未完)

题意

有n个只含左右括号的字符串(长度不超过300),求选出几个字符串组成一个括号匹配的字符串的最大长度。

题解

开始考虑时,想用三个状态变量表示状态。定义dp(i,j,k)表示处理到第i个字符串,待匹配的左右括号数分被为ij时的最大长度。状态转移方程为dp(i,j,k)=max(dp(i-1,j,k),dp(i-1,j',k')+len(i),dp(i-1,j'',k'')+len(i))。实现时用刷表的方法。

然而这样写有一个很大的问题,这种dp只能处理左右待匹配的括号数最大只有300的情况。而本题最多会有90000个待匹配的左(右)括号。这样的话,开三维数组开不下,而且时间复杂度也太高。

之后看了网上代码,发现了一种优化算法。

再次分析问题可以发现,所有的括号字符串可以归纳为如下形式*)*)*(*(*,*表示左右括号已匹配的子段,不同字符串未匹配的括号数不同,且右括号在左,左括号在右。而要将几个字符串组合起来,第一字符串必然为(*(*,最后一个必然为*)*)

这样,就可以得到另一种解决这个问题的步骤。先将所有字符串排列成如下形式:((((,..,((,..., )(((,...,))((,...,)))(,...,)),...,)))

之后依次选取字符串,这样就可以保证已经拼好的字符串中只有未匹配的左括号。整个过程中,拼接字符串的未匹配左括号数会先增大,之后减小到0。由此状态得到简化,可定义为dp(i,j)表示处理到第i个字符串且已拼好的字符串的待匹配左括号数为j的最大长度。状态转移方程也就可以直接参考01背包。


回顾整个过程,个人认为比较重要的一步是将字符串进行了排序(预处理)。这种预处理与题意紧密相连,可以看作一种找规律。

在该题中,这种规律的目的是减少我们要考虑的决策。在自己最开始考虑的状态中,要考虑将字符串拼接到左边还是右边,或是不拼。而在之后的状态中,只需要考虑是否将该字符串拼到右边。

不过这种方法是否是一个普遍方法还有待检验。


代码细节日后补。

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long ll;

const int maxn = 305;

int dp[maxn][maxn*maxn];
int n;
int len[maxn],L[maxn],R[maxn],r[maxn];

bool cmp(const int a,const int b){
    if(!(R[a]) ^ !(R[b])) return R[a]<R[b];
    if(!(L[a]) ^ !(L[b])) return L[a]>L[b];
    return L[a]-R[a]>L[b]-R[b];
}

int main(){
    scanf("%d", &n);
    char st[maxn];
    for(int i = 1; i<=n; i++){
        r[i] = i;
        scanf("%s", st);
        len[i] = strlen(st);
        L[i] = R[i] = 0;
        int cnt = 0;
        for(int j = 0; j<len[i]; j++){
            if(st[j]=='(') ++cnt;
            else{
                if(cnt>0) --cnt;
                else R[i]++;
            }
        }
        L[i] = cnt;
    }
    sort(r+1, r+n+1, cmp);//这里用了外排序
    int MAX = maxn*maxn;
    for(int i = 0; i<=n; i++)
    for(int j = 0; j<MAX; j++)
        dp[i][j] = -0x3f3f3f3f;//...
    dp[0][0] = 0;
    for(int i = 1; i<=n; i++){
        int dv = L[r[i]]-R[r[i]];
        for(int j = 0; j<MAX; j++){
            dp[i][j] = max(dp[i-1][j],dp[i][j]);
            if(j-dv<MAX && j>=L[r[i]]) dp[i][j] = max(dp[i][j], dp[i-1][j-dv]+len[r[i]]);
        }
    }
    printf("%d", dp[n][0]);
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值