首先来个题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=737
有个更难的版本(不过很好玩):http://www.lydsy.com/JudgeOnline/problem.php?id=3229
题目:
3 1 2 3 7 13 7 8 16 21 4 18
9 239
最普通的算法O(n^3):
1 #include <fstream> 2 #include <iostream> 3 #include <cstdio> 4 #include <cstring> 5 #include <cstdlib> 6 #include <cmath> 7 using namespace std; 8 9 const int N=205; 10 const int INF=0x7fffffff; 11 int n; 12 int a[N],sum[N],dp[N][N]; 13 14 void f(); 15 16 int main(){ 17 //freopen("D:\\input.in","r",stdin); 18 while(~scanf("%d",&n)){ 19 sum[0]=0; 20 for(int i=1;i<=n;i++){ 21 scanf("%d",&a[i]); 22 sum[i]=sum[i-1]+a[i]; 23 } 24 f(); 25 printf("%d\n",dp[1][n]); 26 } 27 return 0; 28 } 29 void f(){ 30 for(int i=1;i<=n;i++) dp[i][i]=0; 31 for(int r=1;r<n;r++){ 32 for(int i=1;i<n;i++){ 33 int j=i+r; 34 if(j>n) break; 35 dp[i][j]=INF; 36 for(int k=i;k<=j;k++){ 37 dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]); 38 } 39 dp[i][j]+=sum[j]-sum[i-1]; 40 } 41 } 42 }
其中,dp[i][j]代表i到j堆的最优值,sum[i]代表第1堆到第i堆的数目总和。有:dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j])+sum[j]-sum[i-1]。
考虑四边形不等式优化:接近O(n^2):
1 #include <fstream> 2 #include <iostream> 3 #include <cstdio> 4 #include <cstring> 5 #include <cstdlib> 6 #include <cmath> 7 using namespace std; 8 9 const int N=205; 10 const int INF=0x7fffffff; 11 int n; 12 int a[N],sum[N],dp[N][N],s[N][N]; 13 14 void f(); 15 16 int main(){ 17 //freopen("D:\\input.in","r",stdin); 18 while(~scanf("%d",&n)){ 19 sum[0]=0; 20 for(int i=1;i<=n;i++){ 21 scanf("%d",&a[i]); 22 sum[i]=sum[i-1]+a[i]; 23 } 24 f(); 25 printf("%d\n",dp[1][n]); 26 } 27 return 0; 28 } 29 void f(){ 30 for(int i=1;i<=n;i++) dp[i][i]=0,s[i][i]=i; 31 for(int r=1;r<n;r++){ 32 for(int i=1;i<n;i++){ 33 int j=i+r; 34 if(j>n) break; 35 dp[i][j]=INF; 36 for(int k=s[i][j-1];k<=s[i+1][j];k++){ 37 if(dp[i][j]>dp[i][k]+dp[k+1][j]){ 38 dp[i][j]=dp[i][k]+dp[k+1][j]; 39 s[i][j]=k; 40 } 41 } 42 dp[i][j]+=sum[j]-sum[i-1]; 43 } 44 } 45 }
优化:原状态转移方程中的k的枚举范围便可以从原来的(i~j-1)变为(s[i,j-1]~s[i+1,j])。
解释下:
四边形不等式优化动态规划原理:
1.当决策代价函数w[i][j]满足w[i][j]+w[i’][j’]<=w[I;][j]+w[i][j’](i<=i’<=j<=j’)时,称w满足四边形不等式.当函数w[i][j]满足w[i’][j]<=w[i][j’] i<=i’<=j<=j’)时,称w关于区间包含关系单调.
2.如果状态转移方程m为且决策代价w满足四边形不等式的单调函数(可以推导出m亦为满足四边形不等式的单调函数),则可利用四边形不等式推出最优决策s的单调函数性,从而减少每个状态的状态数,将算法的时间复杂度由原来的O(n^3)降低为O(n^2).方法是通过记录子区间的最优决策来减少当前的决策量.令:
s[i][j]=max{k | ma[i][j] = m[i][k-1] + m[k][j] + w[i][j]}
由于决策s具有单调性,因此状态转移方程可修改为:
证明过程: (转载)
设m[i,j]表示动态规划的状态量。
m[i,j]有类似如下的状态转移方程:
m[i,j]=opt{m[i,k]+m[k,j]}(i≤k≤j)
如果对于任意的a≤b≤c≤d,有m[a,c]+m[b,d]≤m[a,d]+m[b,c],那么m[i,j]满足四边形不等式。
以上是适用这种优化方法的必要条件
对于一道具体的题目,我们首先要证明它满足这个条件,一般来说用数学归纳法证明,根据题目的不同而不同。
通常的动态规划的复杂度是O(n3),我们可以优化到O(n2)
设s[i,j]为m[i,j]的决策量,即m[i,j]=m[i,s[i,j]]+m[s[i,j]+j]
我们可以证明,s[i,j-1]≤s[i,j]≤s[i+1,j] (证明过程见下)
那么改变状态转移方程为:
m[i,j]=opt{m[i,k]+m[k,j]} (s[i,j-1]≤k≤s[i+1,j])
复杂度分析:不难看出,复杂度决定于s的值,以求m[i,i+L]为例,
(s[2,L+1]-s[1,L])+(s[3,L+2]-s[2,L+1])…+(s[n-L+1,n]-s[n-L,n-1])=s[n-L+1,n]-s[1,L]≤n
所以总复杂度是O(n2)
对s[i,j-1]≤s[i,j]≤s[i+1,j]的证明:
设mk[i,j]=m[i,k]+m[k,j],s[i,j]=d
对于任意k<d,有mk[i,j]≥md[i,j](这里以m[i,j]=min{m[i,k]+m[k,j]}为例,max的类似),接下来只要证明mk[i+1,j]≥md[i+1,j],那么只有当s[i+1,j]≥s[i,j]时才有可能有ms[i+1,j][i+1,j]≤md[i+1,j]
(mk[i+1,j]-md[i+1,j]) - (mk[i,j]-md[i,j])
=(mk[i+1,j]+md[i,j]) - (md[i+1,j]+mk[i,j])
=(m[i+1,k]+m[k,j]+m[i,d]+m[d,j]) - (m[i+1,d]+m[d,j]+m[i,k]+m[k,j])
=(m[i+1,k]+m[i,d]) - (m[i+1,d]+m[i,k])
∵m满足四边形不等式,∴对于i<i+1≤k<d有m[i+1,k]+m[i,d]≥m[i+1,d]+m[i,k]
∴(mk[i+1,j]-md[i+1,j])≥(mk[i,j]-md[i,j])≥0
∴s[i,j]≤s[i+1,j],同理可证s[i,j-1]≤s[i,j]
证毕
扩展:
以上所给出的状态转移方程只是一种比较一般的,其实,很多状态转移方程都满足四边形不等式优化的条件。
解决这类问题的大概步骤是:
0.证明w满足四边形不等式,这里w是m的附属量,形如m[i,j]=opt{m[i,k]+m[k,j]+w[i,j]},此时大多要先证明w满足条件才能进一步证明m满足条件
1.证明m满足四边形不等式
2.证明s[i,j-1]≤s[i,j]≤s[i+1,j]
GarsiaWachs算法优化:
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 using namespace std; 5 6 const int N = 205; 7 int stone[N]; 8 int n,t,ans; 9 10 void combine(int k); 11 12 int main(){ 13 while(cin>>n){ 14 for(int i=0;i<n;i++) 15 scanf("%d",stone+i); 16 t = 1; 17 ans = 0; 18 for(int i=1;i<n;i++){ 19 stone[t++] = stone[i]; 20 while(t >= 3 && stone[t-3] <= stone[t-1]) 21 combine(t-2); 22 } 23 while(t > 1) 24 combine(t-1); 25 printf("%d\n",ans); 26 } 27 return 0; 28 } 29 void combine(int k){ 30 int tmp = stone[k] + stone[k-1]; 31 ans += tmp; 32 for(int i=k;i<t-1;i++) 33 stone[i] = stone[i+1]; 34 t--; 35 int j = 0; 36 for(j=k-1;j>0 && stone[j-1] < tmp;j--) 37 stone[j] = stone[j-1]; 38 stone[j] = tmp; 39 while(j >= 2 && stone[j] >= stone[j-2]){ 40 int d = t - j; 41 combine(j-1); 42 j = t - d; 43 } 44 }
解释:
对于石子合并问题,有一个最好的算法,那就是GarsiaWachs算法。时间复杂度为O(n^2)。
它的步骤如下:
设序列是stone[],从左往右,找一个满足stone[k-1] <= stone[k+1]的k,找到后合并stone[k]和stone[k-1],再从当前位置开始向左找最大的j,使其满足stone[j] > stone[k]+stone[k-1],插到j的后面就行。一直重复,直到只剩下一堆石子就可以了。在这个过程中,可以假设stone[-1]和stone[n]是正无穷的。
1 #include <fstream> 2 #include <iostream> 3 #include <cstdio> 4 #include <cstring> 5 #include <cstdlib> 6 #include <cmath> 7 using namespace std; 8 9 const int N = 205; 10 const int INF = 0x7fffffff; 11 12 int stone[N]; 13 int n,t,ans; 14 15 void combine(int k) 16 { 17 int tmp = stone[k] + stone[k-1]; 18 ans += tmp; 19 for(int i=k;i<t-1;i++) 20 stone[i] = stone[i+1]; 21 t--; 22 int j = 0; 23 for(j=k-1;stone[j-1] < tmp;j--) 24 stone[j] = stone[j-1]; 25 stone[j] = tmp; 26 while(j >= 2 && stone[j] >= stone[j-2]) 27 { 28 int d = t - j; 29 combine(j-1); 30 j = t - d; 31 } 32 } 33 34 int main() 35 { 36 //freopen("D:\\input.in","r",stdin); 37 while(~scanf("%d",&n)) 38 { 39 for(int i=1;i<=n;i++) 40 scanf("%d",stone+i); 41 stone[0]=INF; 42 stone[n+1]=INF-1; 43 t = 3; 44 ans = 0; 45 for(int i=3;i<=n+1;i++) 46 { 47 stone[t++] = stone[i]; 48 while(stone[t-3] <= stone[t-1]) 49 combine(t-2); 50 } 51 while(t > 3) combine(t-1); 52 printf("%d\n",ans); 53 } 54 return 0; 55 }
小细节在于把数列前后加两个值INF和INF-1。这样就不需要每次判别上下界。至于-1,组合堆的最后一步里体现。