区间DP

先在小区间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

原文地址:https://www.cnblogs.com/Untergehen/p/14342335.html