dp的平行四邊形優化

證實過程轉載自charliezhi2007的博客c++


題目連接算法

備用連接數組

分析:很簡單的區間dp,狀態轉移方程dp[i][j]=min(dp[i][j],dp[i][s]+dp[s+1][j]+sum[j]-sum[i-1]),其中dp[i][j]表示區間[i,j]的最小值,sum數組爲前綴和優化

code:ui

#include<bits/stdc++.h>
using namespace std;
const int inf=1<<30;
int dp[1005][1005],a[1005],sum[1005];
int main()
{
    int n,i,j,k,s;
    scanf("%d",&n);
    for(i=1;i<=n;i++) dp[i][i]=0;
    sum[0]=0; 
    for(i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        sum[i]=sum[i-1]+a[i];
    }
    for(k=1;k<n;k++)
    {
        for(i=1;i<=n-k;i++)
        {
            j=i+k;
            dp[i][j]=inf;
            for(s=i;s<j;s++) 
                dp[i][j]=min(dp[i][j],dp[i][s]+dp[s+1][j]+sum[j]-sum[i-1]);
        }
    }
    printf("%d\n",dp[1][n]);
    return 0;
}

BUT--------------

這樣作會TLE!!!

怎麼辦呢?來,咱們來看代碼的循環部分:spa

for(k=1;k<n;k++)
{
    for(i=1;i<=n-k;i++)
    {
        j=i+k;
        dp[i][j]=inf;
        for(s=i;s<j;s++) 
            dp[i][j]=min(dp[i][j],dp[i][s]+dp[s+1][j]+sum[j]-sum[i-1]);
    }
}

前兩層循環枚舉距離和起點,沒法優化,可是第三層循環尋找斷點是能夠優化的。怎麼作呢?code

能夠再開一個s數組來記錄每一個區間的最優斷點,而後s(尋找斷點)每次只從s[i][j-1]循環到s[i+1][j],這樣時間複雜度能夠從O(N^3)降到近似O(N^2)。htm

如何證實這樣的循環來找斷點是對的呢?blog

讓咱們請上charliezhi2007大佬~get


注:m[i][j]即dp[i][j]

Minval算法證實+註釋(s [ i ] [ j - 1 ] ~ s [ i + 1 ] [ j ] )s表示區間(i , j)中的最優斷點

設a , b , c , d(a<=b<=c<=d)    //結論是兩邊之和大於第三邊(四邊形)
m(a,c) + m(b,d) <= m(a,d) + m(b,c)    //四邊形對邊相等
s[ i ][j-1] <= s[ i ][ j ] <= s[i+1][ j ]

s[ i ][ j ]<=s[i+1][ j ]思考
d=s[ i ][ j ] ,i < i + 1 <= k < d      k<-[ i , j ]        //設d爲斷點位置,假設k小於d,k爲i,j中任意斷點                           
mk(i,j) = m(i,k) + m(k + 1,j) + sum(i,j) //表示以k爲斷點i,j合併的代價   sum是i到j全部值得和
md(i,j) = m(i,d) + m(d + 1,j) + sum(i,j)    //表示以d爲斷點i,j合併的代價(代價最小)
mk(i,j) >= md(i,j) > 0           //d爲斷點將i,j合併的代價最小由於d是最優斷點     
=> mk(i,j) - md(i,j) > 0                            

(mk(i + 1,j) - md(i + 1,j)) - (mk(i,j) - md(i,j))         //判斷k>d 或 d>k由於上面假設k<d要證實
=(mk(i + 1,j) + md(i,j)) - (md(i + 1,j) + mk(i,j))     //將係數爲負數的項,係數爲正數的項放在一塊兒
=    (m(i + 1,k) + m(k + 1,j) + m(i,d) + m(d + 1,j) + sum(i,j) + sum(i + 1,j))
    - (m(i + 1,d) + m(k + 1,j) + m(i,k) + m(k + 1,j)+ sum(i,j) + sum(i + 1,j))      //將式子展開
=>將減號兩邊的m(k + 1,j) 和 m(d + 1,j) 和 sum(i,j) 和 sum(i + 1,j)相互消元
=(m(i + 1,k) + m(i,d)) - (m(i + 1,d) + m(i,k))
=> i<i + 1<k <d       =       a <b <c <d            //兩式變量相等
=>(m(i + 1,k) + m(i,d)) - (m(i + 1,d) + m(i,k))     =     (m(b,c) + m(a,d)) - (m(b,d) + m(a,c))     >0      //因ad+bc>=ac+bd因此該式大於0
=>(mk(i + 1,j) - md(i + 1,j)) - (mk(i,j) - md(i,j)) >0            //則這個也大於0
因d<=b    則b=s[i + 1][ j ]

下面求s[ i ][j-1]的思路於上面一致,則最終得出k=s[ i ][j-1] ~ s[i-1][ j ]

好的,感謝這位大佬的講解!

而後,咱們就能夠愉快地寫代碼啦!

獻上AC代碼:

#include<bits/stdc++.h>
using namespace std;
const int inf=1<<30;
int dp[1005][1005],a[1005],sum[1005],s[1005][1005];
int main()
{
    int n,i,j,k,ss;
    scanf("%d",&n);
    for(i=1;i<=n;i++) 
    {
        dp[i][i]=0;
        s[i][i]=i;
    }
    sum[0]=0; 
    for(i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        sum[i]=sum[i-1]+a[i];
    }
    for(k=1;k<n;k++)
    {
        for(i=1;i<=n-k;i++)
        {
            j=i+k;
            dp[i][j]=inf;
            for(ss=s[i][j-1];ss<=s[i+1][j];ss++) 
            {
                if(dp[i][ss]+dp[ss+1][j]+sum[j]-sum[i-1]<dp[i][j])
                {
                    dp[i][j]=dp[i][ss]+dp[ss+1][j]+sum[j]-sum[i-1];
                    s[i][j]=ss;
                }
            }
        }
    }
    printf("%d\n",dp[1][n]);
    return 0;
}
相關文章
相關標籤/搜索