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