证实过程转载自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; }