题目描述
现给定 n ( 1 ≤ n ≤ 300 ) n(1\le n\le 300) n(1≤n≤300) 个括号序列,你需要选择若干序列,将它们按一定的顺序从左往右拼接起来,得到一个合法的括号序列,计算可以得到的合法的括号序列的长度的最大值。括号序列仅由小括号组成且长度在 [ 1 , 300 ] [1,300] [1,300] 之间。
算法分析
据说是个套路题,思路来源于 【WF 2016】Swap Space。
如果我们将括号序列内的所有配对括号都删去的话,得到的括号序列一定是由前面若干个(可以为 0 0 0)右括号和后面若干个(可以为 0 0 0)左括号组成。
因此,我们可以以目前左括号的个数作为背包 DP 的容量,序列的长度作为背包 DP 的价值进行转移,然而这些括号序列是无序的……
考虑如何通过排序使得这些括号序列变得有序,进而可以使用背包 DP 计算,显然应当让消去后左边右括号个数比右边左括号个数小的更靠前,这样可以尽量少的出现左括号浪费的情况。
满足此前提后,对于左边右括号个数比右边左括号个数小的括号序列按照左括号个数从小到大排序,对于左边右括号个数比右边左括号个数大的括号序列按照右括号个数从大到小排序,然后直接 DP 即可。
参考:链接。
代码实现
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 305
#define cmax(x,y) x=std::max(x,y)
struct type {
int x,y,l;
bool operator < (const type &rhs) const {
if(x<=y&&rhs.x>rhs.y) return true;
if(x>y&&rhs.x<=rhs.y) return false;
if(x<=y&&rhs.x<=rhs.y) return x<rhs.x;
return y>rhs.y;
}
} arr[MAXN];
char s[MAXN];int f[MAXN*MAXN];
int main() {
int n;scanf("%d",&n);
int sum=0;
for(int i=1;i<=n;++i) {
scanf("%s",s);
int x=0,y=0,l=strlen(s);
for(int j=0;j<l;++j) {
if(s[j]=='(') ++y;
else y?--y:++x;
}
arr[i]=(type){x,y,l};
sum+=y;
}
std::sort(arr+1,arr+1+n);
memset(f,0xc0,sizeof(f));f[0]=0;
for(int i=1;i<=n;++i) {
if(arr[i].x>arr[i].y) for(int j=arr[i].x;j<=sum;++j)
cmax(f[j-arr[i].x+arr[i].y],f[j]+arr[i].l);
else for(int j=sum;j>=arr[i].x;--j)
cmax(f[j-arr[i].x+arr[i].y],f[j]+arr[i].l);
}
printf("%d\n",f[0]);
return 0;
}