题意
你有 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();
}