2018浙江省賽 括號序列
阿新 • • 發佈:2019-01-26
題意
你有 n 個元素排成一行,每個元素都由一個括號 (左括號或右括號) 和一個權值構成,我們將第 i 個元素記作(si; vi),其中 si 為 “(” 或者 “)”,vi 為一個整數 (可能為負數)。
你每次可以選擇一對相鄰的元素,對應的括號為 “()”,即找到一個 k(1 k < n),滿足 sk 為 “(” 且 sk+1 為“)”。你可以交換第 k 和 k + 1 個元素 (包括括號和對應的權值),然後獲得 vk vk+1 的分數。
現在給你初始的元素排列,問你最多能獲得多少的分數。
如果所有數都>0的話,那貪心把所有)都移到最左邊就好了,但是這裡有負數。
首先可以發現
1.答案只取決於原始狀態和最終狀態[可以確定每個')'跨越了幾個'(']
2.原序列中一個右括號右邊的右括號不可能向左移時跨越它
所以dp[i][j]表示到左數第i個‘)’時,最終將它放在第j個‘(’後面,最大收益多少
看起來好像要列舉dp[i-1][k](0<=k<=j),發現每次轉移都是dp[i-1],[0-j]+v[j--當前‘(’數量],這樣可以維護字首最大值和字首和優化一下,把dp轉移降到O(1)
#include<iostream> #include<cstdio> #include<cstring> #define LL long long using namespace std; char s[2020]; LL dp[2020][2020],A[2020],sum[2200]; int len,n,m; LL Max(LL a,LL b){ if (a>b) return a; return b; } void Work(){ memset(sum,0,sizeof(sum)); n=m=0; int i,j,mz=0; scanf("%d",&len); for (i=1;i<=len;i++){ cin>>s[i]; if (s[i]=='(') mz++; } for (i=1;i<=len;i++) scanf("%lld",&A[i]); for (i=1;i<=len;i++){ if (s[i]=='('){ m++; sum[m]=sum[m-1]+A[i]; continue; } n++; for (j=0;j<=m;j++){ dp[n][j]=dp[n-1][j]+(sum[m]-sum[j])*A[i]; if (j) dp[n][j]=Max(dp[n][j],dp[n][j-1]); } for (j=m+1;j<=mz;j++) dp[n][j]=dp[n][j-1];//以後轉移要用 } cout<<dp[n][m]<<endl; } int main(){ int num; cin>>num; while (num--) Work(); }
dp[i][j]只從dp[i-1][j]轉移過來,雖然本題空間不需要優化,但我們發現可以優化掉一維,這樣的話dp陣列需要初始化
#include<iostream> #include<cstdio> #include<cstring> #define LL long long using namespace std; char s[2020]; LL dp[2020],A[2020],sum[2200]; int len,n,m; LL Max(LL a,LL b){ if (a>b) return a; return b; } void Work(){ memset(sum,0,sizeof(sum)); memset(dp,0,sizeof(dp)); n=m=0; int i,j,mz=0; scanf("%d",&len); for (i=1;i<=len;i++){ cin>>s[i]; if (s[i]=='(') mz++; } for (i=1;i<=len;i++) scanf("%lld",&A[i]); for (i=1;i<=len;i++){ if (s[i]=='('){ m++; sum[m]=sum[m-1]+A[i]; continue; } n++; for (j=0;j<=m;j++){ dp[j]=dp[j]+(sum[m]-sum[j])*A[i]; if (j) dp[j]=Max(dp[j],dp[j-1]); } for (j=m+1;j<=mz;j++) dp[j]=dp[j-1]; } cout<<dp[m]<<endl; } int main(){ int num; cin>>num; while (num--) Work(); }