證實過程轉載自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; }
怎麼辦呢?來,咱們來看代碼的循環部分: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; }