1. 程式人生 > >ZOJ 4027 Sequence Swapping(DP+思維)

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;
    }
}