timus1745题解

一、题目链接

http://acm.timus.ru/problem.aspx?space=1&num=1745

二、题意

给定$n$个由'('和')'组成的字符串,每个串最多只能使用$1$次,可以任意改变字符串之间的顺序,要求输出由这些字符串拼接起来可以得到的最大“正规括号序列”的长度,并输出选择方案。

三、思路

 首先,这很显然需要$dp$。所以对字符串按照某种规则排序。容易想到的是,把左括号多的放前面,右括号多的放后面,然而,这只是理想的状态,稍微复杂的样例如下:

 

))()()((
)))((((
()))(((()

 

对于这些复杂的样例,我们没法想清楚所以情况并逐一对其考虑排序,所以,这个地方留下一个坑,先参考这个博客里的神排序规则。

http://www.cnblogs.com/liulangye/archive/2013/10/02/3349523.html

然后,由于$\sum\limits_{i=1}^{n}|s_i| \le 10000$,$n \le 1000$,所以可以做01背包。

在每一个字符串中,记录它的左括号的个数$lb$和右括号的个数$lb$,同时再记录每个串里面已经匹配的最大长度$num$,并把$lb$和$rb$减去$num$。

$dp[i][j]$表示在前$i$个字符串里面选择若干个字符串且左括号个数比右括号个数exactly多$j$个所能获得的最大正规括号序列长度,那么,最终答案就是$dp[n][0]$。

如果采用“多推一”的$dp$方式,那么有:

  如果$dp[i-1][j] \ne -1$,$dp[i][j]=max(dp[i][j],dp[i-1][j])$,

  如果$dp[i-1][j+ns[i].rb-ns[i].lb] \ne -1$,$dp[i][j]=max(dp[i][j],dp[i-1][j+ns[i].rb-ns[i].lb]+ns[i].num+2*ns[i].rb)$

  其中:$1 \le i \le n,0 \le j \le m$。

如果采用“一推多”的$dp$方式,那么有:

  如果$dp[i][j] \ne -1$,$dp[i+1][j]=max(dp[i+1][j],dp[i][j])$,

  $dp[i+1][j-ns[i].rb+ns[i].lb]=max(dp[i+1][j-ns[i].rb+ns[i].lb],dp[i][j]+ns[i].num+2*ns[i].rb)$,

  其中:$0 \le i < n,0 \le j \le m$。

上式中的$m=\sum\limits_{i=1}^{n}|s_i|$。

要注意的是:

1、在一开始输入的时候,要根据所采用的$dp$姿势来确定下标的起点。

2、从个人感觉来看,第一种"多推一"的$dp$姿势状态转移没那么直观,但是找路径更加直观;第二种"一推多"的$dp$姿势状态转移更加直观,但是找路径没那么直观。

2、无论那种$dp$姿势,无论当前括号序列有没有取,都需要记录父节点。

3、要记录路径,所以在状态转移的时候,无论当前“背包”是选还是不选,都应该设置父节点,否则,肯定会错。

4、由于$dp[i][j]$表示在前$i$个字符串里面选择若干个字符串且左括号个数比右括号个数exactly多$j$个所能获得的最大正规括号序列长度,所以一开始要把$dp$数组初始化为$-1$。在初始状态中,只有$dp[0][0]$是合法的,即$dp[0][0]=0$。

5、在"一推多"的$dp$状态转移代码中,有这么一个判断条件$j-ns[i].rb>=0$,这个条件一定不能写成$j-ns[i].rb+ns[i].lb>=0$,因为题目中说明了,在“正规括号序列”的任意前缀中,左括号的数量都不小于右括号的数量。如果写成了第二种条件,那么“))()((”这种样例也会满足条件。

四、代码

1、“一推多”的$dp$

#include<bits/stdc++.h>
using namespace std;
struct node{
    string s;
    int id,lb,rb,num;
}ns[1010];

int func(const node& a){
    int t=a.lb-a.rb;
    return t==0?0:t/abs(t);
}

bool cmp(const node& a,const node& b){
    int lf=func(a),rf=func(b);
    if(lf==1&&rf==1)return a.rb<b.rb;
    else if(lf==-1&&rf==-1)return a.lb>b.lb;
    else return lf>rf;
}

int n, dp[1010][10010],ans[1010],acnt;
short path[1010][10010];
int main(){
//    freopen("input.txt","r",stdin);
    ios::sync_with_stdio(0);
    cin.tie(0);
    int m=0;
    cin>>n;
    for(int i=0;i<n;++i){
        cin>>ns[i].s;
        m+=ns[i].s.size();
        ns[i].id=i+1;
        ns[i].lb=ns[i].rb=ns[i].num=0;
        stack<char> st;
        for(int j=0;j<ns[i].s.size();++j){
            if(ns[i].s[j]=='(')ns[i].lb++;
            else ns[i].rb++;
            if(ns[i].s[j]=='(')st.push('(');
            else if(!st.empty())st.pop(),ns[i].num+=2;
        }
        ns[i].lb-=ns[i].num/2;
        ns[i].rb-=ns[i].num/2;
    }
    sort(ns,ns+n,cmp);
    memset(dp,-1,sizeof(dp));
    memset(path,-1,sizeof(path));
    dp[0][0]=0;
    for(int i=0;i<n;++i){
        for(int j=0;j<=m;++j){
            if(dp[i][j]==-1)continue;
            if(dp[i+1][j]<dp[i][j]){
                dp[i+1][j]=dp[i][j];
                path[i+1][j]=-1;
            }
            if(j-ns[i].rb>=0&&j-ns[i].rb+ns[i].lb<=m
               &&dp[i+1][j-ns[i].rb+ns[i].lb]<dp[i][j]+ns[i].num+2*ns[i].rb){
                dp[i+1][j-ns[i].rb+ns[i].lb]=dp[i][j]+ns[i].num+2*ns[i].rb;
                path[i+1][j-ns[i].rb+ns[i].lb]=j;
            }
        }
    }
    acnt=0;
    for(int i=n,j=0;i>0;--i){
        if(path[i][j]!=-1){
            ans[acnt++]=ns[i-1].id;
            j=path[i][j];
        }
    }
    cout<<(dp[n][0]==-1?0:dp[n][0])<<" "<<acnt<<endl;
    for(int i=acnt-1;i>=0;--i)cout<<ans[i]<<" \n"[i==0];
    return 0;
}

 2、"多推一"的$dp$

#include<bits/stdc++.h>
using namespace std;
struct node{
    string s;
    int id,lb,rb,num;
}ns[1010];

int func(const node& a){
    int t=a.lb-a.rb;
    return t==0?0:t/abs(t);
}

bool cmp(const node& a,const node& b){
    int lf=func(a),rf=func(b);
    if(lf==1&&rf==1)return a.rb<b.rb;
    else if(lf==-1&&rf==-1)return a.lb>b.lb;
    else return lf>rf;
}

int n, dp[1010][10010],ans[1010],acnt;
short path[1010][10010];
int main(){
//    freopen("input.txt","r",stdin);
    ios::sync_with_stdio(0);
    cin.tie(0);
    int m=0;
    cin>>n;
    for(int i=1;i<=n;++i){
        cin>>ns[i].s;
        m+=ns[i].s.size();
        ns[i].id=i;
        ns[i].lb=ns[i].rb=ns[i].num=0;
        stack<char> st;
        for(int j=0;j<ns[i].s.size();++j){
            if(ns[i].s[j]=='(')ns[i].lb++;
            else ns[i].rb++;
            if(ns[i].s[j]=='(')st.push('(');
            else if(!st.empty())st.pop(),ns[i].num+=2;
        }
        ns[i].lb-=ns[i].num/2;
        ns[i].rb-=ns[i].num/2;
    }
    sort(ns+1,ns+n+1,cmp);
    memset(dp,-1,sizeof(dp));
    memset(path,-1,sizeof(path));
    dp[0][0]=0;
    for(int i=1;i<=n;++i){
        for(int j=0;j<=m;++j){
            if(dp[i-1][j]!=-1&&dp[i][j]<dp[i-1][j]){
                dp[i][j]=dp[i-1][j];
                path[i][j]=-1;
            }
            if(j-ns[i].lb>=0&&j+ns[i].rb-ns[i].lb<=m
               &&dp[i-1][j+ns[i].rb-ns[i].lb]!=-1
               &&dp[i][j]<dp[i-1][j+ns[i].rb-ns[i].lb]+ns[i].num+2*ns[i].rb){
                dp[i][j]=dp[i-1][j+ns[i].rb-ns[i].lb]+ns[i].num+2*ns[i].rb;
                path[i][j]=j+ns[i].rb-ns[i].lb;
            }
        }
    }
    acnt=0;
    for(int i=n,j=0;i>0;--i){
        if(path[i][j]!=-1){
            ans[acnt++]=ns[i].id;
            j=path[i][j];
        }
    }
    cout<<(dp[n][0]==-1?0:dp[n][0])<<" "<<acnt<<endl;
    for(int i=acnt-1;i>=0;--i)cout<<ans[i]<<" \n"[i==0];
    return 0;
}

 

转载于:https://www.cnblogs.com/fuzhihong0917/p/9744219.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值