1. 程式人生 > >bzoj 4922 Karp-de-Chant Number

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 可能會炸空間,還是用滾動陣列吧。注意兩個部分滾動的方向不同哦。

#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]); }