區間DP:POJ 2955括號匹配 + NYOJ 737 石子歸併(一) + No.312 Burst Balloons

1.區間DP入門 —— POJ 2955 括號匹配

我們先看一道經典題目:給出一個字符串,僅含()[]四個字符,問整個字符串裏最多有多少個匹配的括號。

Sample Input

((()))

()()()

([]])

)[)(

([][][)

end

Sample Output

6

6

4

0

6

 

2. My solution of POJ 2955

區間dp的要點:

1. dp[i][j]表示從ij最多匹配的括號數。

2. 外重循環枚舉區間長度,內重循環枚舉左端點。

狀態轉移方程:

if(judge(ll,rr))//ij處的括號匹配

dp[ll][rr]=dp[ll+1][rr-1]+2;

3. 枚舉llrr的所有中間點kdp[ll][rr]=max(dp[ll][rr],dp[ll][k]+dp[k+1][rr]);

這一步是必須的,如果沒有這一步,對於以下字符串:

0 1 2 3

( ) ( )

因爲03匹配,我們就使得dp[0][3]=dp[1][2]+2=2了,然而實際上01也是匹配的,23也是匹配的。

這種問題會出現的原因是:

if(judge(ll,rr))

dp[ll][rr]=dp[ll+1][rr-1]+2;

這句狀態轉移方程,只考慮了區間內緊挨着llrr是否匹配,而llrr之間,如果也出現了與端點llrr的括號匹配的情況,則被忽略了。

所以有了以下這部分代碼用於判斷整個llrr區間上與端點匹配的括號,對dp[i][j]的影響:

for(int k=ll; k<=rr; k++)

     dp[ll][rr]=max(dp[ll][rr],dp[ll][k]+dp[k+1][rr]);

 

My AC code

#include <cstdio>
#include <iostream>
#include <cstring>
const int MAX = 100 + 10;
using namespace std;
 
int dp[MAX][MAX];
string arr;
//狀態轉移方程
//if(left與right的括號匹配)
//dp[left][right]=dp[left+1][right-1]+2
 
//同時:
//對於所有k從“left+1到right”
//dp[left][right]=max(dp[left+1][k]+dp[k+1][right-1])+2
 
bool judge(int ll,int rr)
{
    if(arr[ll]=='('&&arr[rr]==')'||arr[ll]=='['&&arr[rr]==']')
        return true;
    else
        return false;
}
 
int main()
{
//        freopen("in.txt","r",stdin);
    while(cin>>arr)
    {
        memset(dp,0,sizeof dp);
        if(arr[0]=='e')
            break;
        int len=arr.size();
        //枚舉區間長度cnt
        for(int cnt=1; cnt<len; cnt++)
        {
            //枚舉左端點
            for(int ll=0; ll+cnt<len; ll++)
            {
                //根據左端點和區間長度確定右端點
                int rr=ll+cnt;
                if(judge(ll,rr))
                    dp[ll][rr]=dp[ll+1][rr-1]+2;
                //枚舉中間點
                for(int k=ll; k<=rr; k++)
                    dp[ll][rr]=max(dp[ll][rr],dp[ll][k]+dp[k+1][rr]);
            }
        }
        cout<<dp[0][len-1]<<endl;
    }
    return 0;
}

3. 區間DP進階 —— NYOJ 737 石子歸併(一) 

給出n堆石子,現在我們只能把相鄰的石子堆合併,每次合併的花費爲2個石子堆的石子個數總和,求合併全部石子堆的最小花費。

樣例輸入

3

1 2 3

7

13 7 8 16 21 4 18

樣例輸出

9

239

 

4. My solution

狀態轉移方程:

dp[ll][rr]=min(dp[ll][rr],dp[ll][mm]+dp[mm+1][rr]+sum[rr]-sum[ll-1]);

其中sum[i]爲數組前i項和。

枚舉K相當於枚舉兩個大堆(經過重重合並)的分割點,那麼狀態轉移方程也就不言而喻了。dp[ll][mm]相當於左面那個堆合併的最小代價,dp[mm+1][rr]是右面堆合併的最小代價。

要合併這兩個大堆又需要sum[ll][rr]的代價,故得以上方程。

 

步驟依舊是:

1. 枚舉區間長度

2. 枚舉左端點

3. 根據狀態轉移方程更新區間dp

 

My AC code

#include <cstdio>
#include <iostream>
#include <cstring>
const int INF=99999999;
const int MAXN = 200 + 10;
int arr[MAXN];
int dp[MAXN][MAXN];
int sum[MAXN];
using namespace std;
 
int main()
{
//        freopen("in.txt","r",stdin);
    int n;
    while(cin>>n)
    {
      memset(dp,0,sizeof dp);
      memset(sum,0,sizeof sum);
         for(int i=1;i<=n;i++) cin>>arr[i],sum[i]=sum[i-1]+arr[i],dp[i][i]=0;
 
         for(int cnt=1;cnt<=n;cnt++)
         {
            for(int ll=1;ll+cnt<=n;ll++)
            {
               int rr=ll+cnt;
               dp[ll][rr]=INF;
               for(int mm=ll;mm<rr;mm++)
               {
                  int tmp=sum[rr]-sum[ll-1];
                  dp[ll][rr]=min(dp[ll][rr],dp[ll][mm]+dp[mm+1][rr]+tmp);
               }
            }
         }
         cout<<dp[1][n]<<endl;
    }
    return 0;
}

5. Problem Description of 312. Burst Balloons

Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by array nums. You are asked to burst all the balloons. If the you burst balloon i you will get nums[left] * nums[i] * nums[right] coins. Here left and right are adjacent indices of i. After the burst, the left and right then becomes adjacent.

 

Find the maximum coins you can collect by bursting the balloons wisely.

 

Note:

(1) You may imagine nums[-1] = nums[n] = 1. They are not real therefore you can not burst them.

(2) 0 n 500, 0 nums[i] 100

 

Example:

 

Given [3, 1, 5, 8]

 

Return 167

 

   nums = [3,1,5,8]   -->  [3,5,8]    -->   [3,8]   -->   [8]     --> []

   coins =  3*1*5      +  3*5*8    +  1*3*8      + 1*8*1   = 167

 

給你一堆氣球,每個氣球有一個num,每次扎破一個氣球,付出的代價是這個氣球及其左右相鄰的氣球num乘積。現在我們要把這些氣球全部扎破,求最大的代價。

 

6. My solution(區間DP)

和石子歸併不同的是,石子歸併中我們枚舉llrr之間的所有數,是爲了求把llrr分割成兩個區間的點。對於每個這樣的點,我們歸併時都要求它前後兩個區間的dp和和整個區間的和。

石子歸併的轉移方程:

dp[ll][rr]=min(dp[ll][rr],dp[ll][mm]+dp[mm+1][rr]+sum[rr]-sum[ll-1]);

 

然而此題中我們枚舉這個區間最後一個扎破的氣球。對於每個這樣的點,我們扎破它時,除了扎破代價(ll處,rr處和自己的乘積),當然還要計算前後兩個區間和。

轉移方程:

int add=nums[ll-1]*nums[mm]*nums[rr+1];

dp[ll][rr]=max(dp[ll][rr],dp[ll][mm-1]+dp[mm+1][rr]+add);

 

這裏預處理一下,令nums裏多加兩個數,第一個數和最後一個數是1,便於處理。

My AC code

class Solution
{
private:
    int dp[600][600];
    int len;
public:
    void init(vector<int>& nums)
    {
        memset(dp,0,sizeof dp);
        len=nums.size();
        nums.push_back(1),nums.push_back(1);
        for(int i=len; i>=1; i--)
            nums[i]=nums[i-1];
        nums[0]=1;
    }
    void solve(vector<int>& nums)
    {
        for(int cnt=0; cnt<=len; cnt++)
        {
            for(int ll=1; ll+cnt<=len; ll++)
            {
                int rr=ll+cnt;
                for(int mm=ll; mm<=rr; mm++)
                {
                    int add=nums[ll-1]*nums[mm]*nums[rr+1];
                    dp[ll][rr]=max(dp[ll][rr],dp[ll][mm-1]+dp[mm+1][rr]+add);
                }
            }
        }
    }
    int maxCoins(vector<int>& nums)
    {
        init(nums);
        solve(nums);
        return dp[1][len];
    }
};


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章