大致题意:给你一些括号,有左括号有右括号,每一个括号对应一个数值vi。当左右括号i、j相邻并且左括号在左、右括号在右,你可以选择交换这两个括号的位置,并且产生一个vi*vj的权值。交换次数不限,现在问你能够产生的最大权值和是多少。
首先,对于左括号来说,如果往右移了一位,即与某一个右括号交换了,那么就一定不会交换回来。这是一个很明显的无后效性,因此考虑dp。但是有另外一个问题,每一次的交换会对括号的序列发生改变,直接dp可能又会产生后效性。所以得从最后结果来考虑。
容易知道,由于交换一定是要左右括号配上之后才能够交换,所以左后的结果相当于是,所有左括号内部的相对位置不变,所有右括号内部的相对位置也不变。于是,我就可以不考虑中间的顺序,我直接考虑每个最后左括号相对所有右括号的位置。如果初始时某个左括号右边有i个右括号,最后又j个右括号,其中j<=i,那么产生的代价就是vi*(s[i]-s[j]),其中s表示右括号权值的后缀和。
如此一来,我们令dp[i][j]表示第i个左括号,最终的在第j个括号右边的最大权值和。于是可以得到转移方程dp[i][j]=max(dp[i-1][k]+s[pos[i]]-s[j+1]),其中pos[i]表示左括号i的初始位置,s表示右括号的后缀和。可以看到这个时间复杂度是O(N^3)的,但是显然,由于增加值s[pos[i]]-s[j+1]是固定的,每次转移只需要找最大的dp[i-1][k]即可,而这个最大值也是可以简单的维护的。总的复杂度可以做到O(N^2)。具体见代码:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define LL long long
#define N 1010
using namespace std;
LL sum[N],v[N],w[N][N],dp[N][N];
char s[N];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int T; cin>>T;
while(T--)
{
int m=0,n;
cin>>n>>s+1;
LL tot=0,t=0,ans=0;
memset(sum,0,sizeof(sum));
for(int i=1;i<=n;i++) cin>>v[i];
for(int i=1;i<=n;i++)
if (s[i]==')') sum[++tot]=v[i];
for(int i=tot-1;i>=1;i--) sum[i]+=sum[i+1];
for(int i=0;i<=n;i++)
for(int j=0;j<=tot+1;j++)
dp[i][j]=w[i][j]=-INF;
memset(w[0],0,sizeof(w[0]));
for(int i=n;i>=1;i--)
{
if (s[i]==')') {t++;continue;}
m++;
for(int j=tot;j>=tot-t;j--)
{
dp[m][j]=w[m-1][j]+(sum[tot-t+1]-sum[j+1])*v[i];
ans=max(dp[m][j],ans);
}
for(int j=tot;j>=0;j--) w[m][j]=max(w[m][j+1],dp[m][j]);
}
cout<<ans<<endl;
}
}