bzoj 4922 Karp-de-Chant Number

题意

给出n个括号序列,你从中选出若干个并以任意方式拼接得到的合法括号序列最长是多少
n,|S|<=300

题解

先把输入的括号序列简化成一个三元组(r,l,len)表示最后左边剩下了r个右括号,右边剩下了l个左括号,原长为len (很绞是不是?)
于是我们可以很轻易的得到一个N^3的dp,d[j]表示还剩j个左括号未匹配的最大长度,然后依次枚举,如果j>=r那么,d[j-r+l]=max(d[j]+len)
然而这个算法是错的
因为你不能保证不会选到之前的用过的串
状压?不存在的!
所以此题到这里就似乎无解了

然而并不是,我们考虑一下,用贪心的方法来解决
考虑这道题BZOJ3709
考虑j值的变化情况,可以发现似乎也就相当于这道题打怪一样,先减去r[i]的血,再加上l[i]
那么我们这道题贪心的思路也就是差不多的
首先考虑那些l[i]>=r[i]的,他们会使i最后增加,既然都是增加的话,我们就要尽可能的使得他的前提条件(j>=r[i])能够得到满足,那我们就先处理那些r[i]小的
(其实也可以这样简单考虑, 第一个肯定r[i]=0)
然后考虑那些l[i]<r[i]的,我们从后往前考虑,我们假装我们又在做另外一个dp:f[j]表示右括号数为j时的代价,那么我们通过类似的分析可以得到,越靠后的肯定l[i]越小越好,也即是说,从前往后考虑,l[i]越大越好
(也可以考虑最后一个肯定l[i]=0)
于是我们就先按上述方法排序,然后做前述的dp即可(其实你可以把那个dp想成01背包)
注意到 30 0 3 300^3 3003可能会炸空间,还是用滚动数组吧。注意两个部分滚动的方向不同哦。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=305;
int n;
int d[N*N];
bool vis[N*N];
struct node{
    int l,r,len,sign;
}p[N];
char s[N][N];
bool cmp(node a,node b){
    if(a.sign!=b.sign)
        return a.sign>b.sign;
    if(a.sign)
        return a.r<b.r;
    return a.l>b.l;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%s",s[i]+1);
    int tot=0;
    for(int i=1;i<=n;i++){
        int m=strlen(s[i]+1);
        p[i].len=m;
        int t=0;
        for(int j=1;j<=m;j++){
            if(s[i][j]=='(')
                t++;
            else{
                if(t)
                    t--;
                else
                    p[i].r++;
            }
        }
        p[i].l=t;
        p[i].sign=p[i].l>=p[i].r;
        tot+=t;
    }
    sort(p+1,p+n+1,cmp);
    vis[0]=1;
    for(int i=1;i<=n;i++){
        int z=p[i].l-p[i].r;
        if(p[i].sign){
            for(int j=tot;j>=p[i].r;j--)
                if(vis[j]){
                    d[j+z]=max(d[j+z],d[j]+p[i].len);
                    vis[j+z]=1;
                }
        }
        else{
            for(int j=p[i].r;j<=tot;j++){
                if(vis[j]){
                    d[j+z]=max(d[j+z],d[j]+p[i].len);
                    vis[j+z]=1;
                }
            }
        }
    }
    printf("%d\n",d[0]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值