先在小区间DP得到最优解,再合并小区间求大区间最优解,一般把左右两个相邻的子区间合并,需要从小到大枚举所有可能的区间。
https://vjudge.net/problem/51Nod-1021
先举个栗子:
有一二三堆石子,这个时候第一步有两种合并的方法,分别是1和2合并,2和3合并,那么最后应该选择两者中较小的那一个方案,也就是:
min(dp[1][1]+dp[2][3],dp[1][2]+dp[3][3])
这里的dp[2][3]指的是第二堆石子与第三堆石子合并,把整个石子的排列看作是一个大区间的话,2到3石子就是其中的一个小区间,不断将小区间合并,最后得到大区间的最小值,那么三堆石子的最小值就应该是:
dp[1][3]=min(dp[1][1]+dp[2][3],dp[1][2]+dp[3][3])+sum[1][3](这里的sum是从1到3的和)
那么问题的求解就是需要求得dp[1][n]的最小值,由三堆石子推广得到的状态转移方程为:
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j])+sum[i][j-i+1](这里也可以是sum[j]-sum[i-1])
具体代码:
#include<bits/stdc++.h> using namespace std; const int INF=1<<30;//取一个无穷大 const int N=300; int sum[N],n;int Minval(){
int dp[N][N];
for(int i=1;i<=n;i++)
dp[i][i]=0;
for(int len=1;len<=n;len++){//len是i到j的距离
for(int i=1;i<=n-len;i++){
int j=i+len;
dp[i][j]=INF;
for(int k=i;k<j;k++)//i和j之间用k来分割,就像是栗子中三堆石子里的第二堆
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
}
}
return dp[1][n];
}int main()
{
while(~scanf("%d",&n)){
sum[0]=0;
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
sum[i]=sum[i-1]+x;
}
printf("%d ",Minval());
}
return 0;
}
那么,变个形:
http://120.78.128.11/Problem.jsp?pid=2385
这道题与上面那一道的区别是石子不再是排列成一排,而是被摆放为了一个环,那么就有一个头尾相接可合并的变化,如果再次看作一排的话,那么这一排的头和尾就是不确定的,有可能是从1到n,也有可能是从2到n再到1,3到n再到2,按照这样的规律,我们可以让1到n之后再加上一排1到n,这样就能把环中头尾相接的情况考虑到,剩下的步骤就和1到n的链状DP相同了。
放代码:
#include<bits/stdc++.h> using namespace std;#define INF 0x3f3f3f
int sum[305],x[305];
int dp[605][605];
int n;int main()
{
scanf("%d",&n);
memset(sum,0,sizeof(sum));
memset(dp,INF,sizeof(dp));
for(int i=1;i<=n;i++){
scanf("%d",&x[i]);
sum[i]=sum[i-1]+x[i];
dp[i][i]=0;
}
for(int i=1;i<=n;i++){
sum[i+n]=sum[i+n-1]+x[i];//这里相当于再加上了一排1到n,需要把sum和dp重新赋值
dp[i+n][i+n]=0;
}
for(int len=1;len<=n;len++){//这一段同上
for(int i=1;i+len<=2*n;i++){
int j=i+len-1;
for(int k=i;k<j;k++)
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
}
}
int f=0xfffffff;
for(int i=1;i<=n;i++){
f=min(f,dp[i][i+n-1]);//在所有情况中找到最小的值
}
printf("%d ",f);
return 0;
}
那么到这里就差不多了,再回头看看代码,发现在寻找最优分割点k时有重复的步骤,可以将找到的最优分割点k保存下来用于下一次循环,这样就能避免重复计算,从而降低复杂度,拿第一道题举栗子:
用s[i][j]表示区间[i,j]中的最优分割点,那么代码就应该是这样的:
int Minval(){ int dp[N][N],s[N][N]; for(int i=1;i<=n;i++){ dp[i][i]=0; s[i][i]=i; } for(int len=1;len<n;len++){ for(int i=1;i<=n-len;i++){ int j=i+len; dp[i][j]=INF; for(int k=s[i][j-1];k<=s[i+1][j];k++){ if(dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]<dp[i][j]){ dp[i][j]=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]; s[i][j]=k; } } } } return dp[1][n]; }
EOF