1. 程式人生 > >區間dp模型(石子歸併,括號匹配,整數劃分)

區間dp模型(石子歸併,括號匹配,整數劃分)

區間dp顧名思義就是在一個區間上進行的一系列動態規劃。對一些經典的區間dp總結在這裡。

1) 石子歸併問題

描述:有N堆石子排成一排,每堆石子有一定的數量。現要將N堆石子併成為一堆。合併的過程只能每次將相鄰的兩堆石子堆成一堆,每次合併花費的代價為這兩堆石子的和,經過N-1次合併後成為一堆。求出總的代價最小值。

分析:要求n個石子歸併,我們根據dp的思想劃分成子問題,先求出每兩個合併的最小代價,然後每三個的最小代價,依次知道n個。

定義狀態dp [ i ] [ j ]為從第i個石子到第j個石子的合併最小代價。

那麼dp [ i ] [ j ] = min(dp [ i ] [ k ] + dp [ k+1 ] [ j ])

那麼我們就可以從小到大依次列舉讓石子合併,直到所有的石子都合併。

這個問題可以用到平行四邊形優化,用一個s【i】【j】=k 表示區間 i---j 從k點分開才是最優的,這樣的話我們就可以優化掉一層複雜度,變為O(n^2).

程式碼:

#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 210
int dp[N][N],sum[N];
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        int a[N];sum[0]=0;
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            sum[i]=sum[i-1]+a[i];
        }
        memset(dp,0,sizeof(dp));
        int i,j,l,k;
		for(l = 2; l <= n; ++l)
		{
			for(i = 1; i <= n - l + 1; ++i)
			{
				j = i + l - 1;
				dp[i][j] = 2100000000;
				for(k = i; k < j; ++k)
				{
					dp[i][j] = std::min(dp[i][j],dp[i][k] + dp[k + 1][j] + sum[j] - sum[i-1]);
				}
			}
		}
		printf("%d\n", dp[1][n]);
    }
    return 0;
}

平行四邊形優化程式碼:

#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 210
int dp[N][N],sum[N],s[N][N];
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        int a[N];sum[0]=0;
        memset(s,0,sizeof(s));
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            s[i][i]=i;
            sum[i]=sum[i-1]+a[i];
        }
        memset(dp,0,sizeof(dp));
        int i,j,l,k;
		for(l = 2; l <= n; ++l)
		{
			for(i = 1; i <= n - l + 1; ++i)
			{
				j = i + l - 1;
				dp[i][j] = 2100000000;
				for(k = s[i][j-1]; k <= s[i+1][j]; ++k)
				{
				    if(dp[i][j]>dp[i][k] + dp[k + 1][j] + sum[j] - sum[i-1])
                    {
                        dp[i][j]=dp[i][k] + dp[k + 1][j] + sum[j] - sum[i-1];
                        s[i][j]=k;
                    }
				}
			}
		}
		printf("%d\n", dp[1][n]);
    }
    return 0;
}



2)括號匹配

題目連結:

描述:給出一串的只有‘(’ ‘)’  '[‘  ']'四種括號組成的串,讓你求解需要最少新增括號數讓串中的所有括號完全匹配。

分析:我們求出這個串的最大匹配,然後串的總長度-最大匹配就是答案。

方法1:首先能想到的是轉化成LCS(最長公共子序列),列舉中間點,求所有的LCS中的最大值 * 2就是最大匹配。但是複雜度較高,光LCS一次就O(n^2)的複雜度。

方法2:

首先考慮怎麼樣定義dp讓它滿足具有通過子結構來求解、

定義dp [ i ] [ j ] 為串中第 i 個到第 j 個括號的最大匹配數目

那麼我們假如知道了 i 到 j 區間的最大匹配,那麼i+1到 j+1區間的是不是就可以很簡單的得到。

那麼 假如第 i 個和第 j 個是一對匹配的括號那麼dp [ i ] [ j ] = dp [ i+1 ] [ j-1 ] + 2 ;

那麼我們只需要從小到大列舉所有 i 和 j 中間的括號數目,然後滿足匹配就用上面式子dp,然後每次更新dp [ i ] [ j ]為最大值即可。

更新最大值的方法是列舉 i 和 j 的中間值,然後讓 dp[ i ] [ j ] = max ( dp [ i ] [ j ] , dp [ i ] [ f ] + dp [ f+1 ] [ j ] ) ;

程式碼:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <string>
using namespace std;
const int  N = 120;
int dp[N][N];
int main()
{
    string s;
    while(cin>>s)
    {
        if(s=="end")
            break;
        memset(dp,0,sizeof(dp));
        for(int i=1;i<s.size();i++)
        {
            for(int j=0,k=i;k<s.size();j++,k++)
            {
                if(s[j]=='('&&s[k]==')' || s[j]=='['&&s[k]==']')
                    dp[j][k]=dp[j+1][k-1]+2;
                for(int f=j;f<k;f++)
                    dp[j][k]=max(dp[j][k],dp[j][f]+dp[f+1][k]);
            }
        }
        cout<<dp[0][s.size()-1]<<endl;
    }
    return 0;
}

3)整數劃分問題

題目描述:給出兩個整數 n , m ,要求在 n 中加入m - 1 個乘號,將n分成m段,求出這m段的最大乘積

分析:根據區間dp的思想,我們定義dp [ i ] [ j ]為從開始到 i 中加入 j 個乘號得到的最大值。

那麼我們可以依次計算加入1----m-1個乘號的結果

而每次放入x個乘號的最大值只需列舉第x個乘號的放的位置即可

dp [ i ] [ j ]  = MAX (dp [ i ] [ j ] , dp [ k ] [ j-1 ] * a [ k+1 ] [ i ] ) ;

程式碼:

#include <cstdio>
#include <cstring>
#define MAX(a,b) a>b?a:b
long long a[20][20];
long long dp[25][25];
int main()
{
    int T,m;
    scanf("%d",&T);
    getchar();
    while(T--)
    {
        char s[22];
        scanf("%s",s+1);
        scanf("%d",&m);
        int l=strlen(s),ok=1;
        memset(a,0,sizeof(a));
        for(int i=1;i<l;i++)
        {
            if(s[i]=='0')
                ok=0;
            for(int j=i;j<l;j++)
            {
                a[i][j]=a[i][j-1]*10+(s[j]-'0');
            }
        }
        if(ok==0&&l-1==m||l-1<m)
        {
            printf("0\n");continue;
        }
        long long x,ans;
        memset(dp,0,sizeof(dp));
        for(int i=0;i<l;i++)
            dp[i][1]=a[1][i];
        ans=0;
        if(m==1)
            ans=dp[l-1][1];
        for(int j=2;j<=m;j++)
        {
            for(int i=j;i<l;i++)
            {
                ans=a[i][i];
                for(int k=1;k<i;k++)
                {
                    dp[i][j]=MAX(dp[i][j],dp[k][j-1]*a[k+1][i]);
                }
            }
        }
        printf("%lld\n",dp[l-1][m]);
    }
    return 0;
}