ZOJ 4027 Sequence Swapping(DP+思维)

          

     大致题意:给你一些括号,有左括号有右括号,每一个括号对应一个数值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;
    }
}

猜你喜欢

转载自blog.csdn.net/u013534123/article/details/80193732