bzoj4922-Karp-de-Chant Number

题意

给出 \(n\) 个括号序列 \(s_i\),求把它们拼成一个合法括号序列,最长的长度是多少。\(n,|s_i|\in [1,300]\)

分析

把左括号看成 1,右括号看成 -1,很容易想到dp f[i][j] 表示前 \(i\) 个括号序列,组成一个和为 \(j\) 的括号序列,且任意一个位置的前缀和都大于等于 0 的最长长度。这其实是一个背包模型。

关键是dp的顺序。一个括号序列进行消括号最后会变成 ))))(((( 这种样子,设它的和为 \(s\) ,右括号个数为 \(m\)

贪心地决定dp顺序。显然先填 \(s\ge 0\) ,后填 \(s<0\) 。在 \(s\ge 0\) 中,为了更优,一定是按 \(m\) 从小到大填,这样可以保证总和不断增加或不变,并且尽量合法。

对于 \(s<0\) 的处理,不妨把答案序列看成两部分,由左边的 \(s\ge 0\) 和右边的 \(s<0\) 拼起来,那么从右边往左边看,贪心地来说,左括号的个数是从小到大的,所以正常来看,左括号从大到小。

由于 \(s\) 可正可负,所以在dp的过程中要考虑从前往后还是从后往前做。

这题关键是用贪心决定dp顺序。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=301;
const int maxm=maxn*maxn;
char s[maxn][maxn];
struct A {
    int sum,mi,len;
    inline bool operator < (const A &b) const {
        if (sum>=0 && b.sum<0) return true; else
        if (b.sum>=0 && sum<0) return false;
        if (sum>=0 && b.sum>=0) return mi>b.mi;
        if (sum<=0 && b.sum<=0) return sum-mi>b.sum-b.mi;
    }
} a[maxn];
int n,f[maxm],mx=0;
inline void Min(int &x,int y) {x=min(x,y);}
inline void Max(int &x,int y) {x=max(x,y);}
inline void deal(int id,char s[]) {
    int &l=a[id].len=strlen(s+1);
    int &t=a[id].mi=0,&sm=a[id].sum=0;
    for (int i=1;i<=l;++i) Min(t,sm+=(s[i]=='('?1:-1));
}
int main() {
#ifndef ONLINE_JUDGE
    freopen("test.in","r",stdin);
#endif
    scanf("%d",&n);
    for (int i=1;i<=n;++i) {
        scanf("%s",s[i]+1);
        deal(i,s[i]);
    }
    sort(a+1,a+n+1);
    memset(f,0xbf,sizeof f);
    f[0]=0;
    for (int i=1;i<=n;++i) {
        mx+=a[i].len;
        if (a[i].sum<0) for (int j=a[i].sum-a[i].mi;j<=mx;++j) Max(f[j],f[j-a[i].sum]+a[i].len); else 
        for (int j=mx;j>=a[i].sum-a[i].mi;--j) Max(f[j],f[j-a[i].sum]+a[i].len);
    }
    printf("%d\n",max(0,f[0]));
    return 0;
}

转载于:https://www.cnblogs.com/owenyu/p/7542352.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值