区间DP
【个人理解】
我觉得所有的DP都是优化的枚举(可能学的少,至少线性DP我觉得是),把一开始的状态结果保存到到数组中,然后推导后面的状态。我觉得区间DP同理,也是一个由短区间推导长区间的一个过程。最典型的例子就是下面的合并石子。
【经典例题】
石子合并有三种类型,
第一种任意位置合并,那个简单贪心做,就行,例题:洛谷 P1090 合并果子。
第二种直线性相邻合并,对应着下面的第一个。
第二种环形下的相邻合并,对应这第二个。
石子合并(一)(直线相邻)
时间限制:1000 ms | 内存限制:65535 KB
难度:3
描述
有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。
输入
有多组测试数据,输入到文件结束。
每组测试数据第一行有一个整数n,表示有n堆石子。
接下来的一行有n(0< n <200)个数,分别表示这n堆石子的数目,用空格隔开
输出
输出总代价的最小值,占单独的一行
样例输入
3
1 2 3
7
13 7 8 16 21 4 18
样例输出
9
239
分析:
如果我们可以把第l堆石子和第r堆石子合并起来,则说明第l堆石子和第r堆石子之间的每一堆都被合并了,这样l和r才能相邻,所以,任意时刻,任意一堆石子均可以用闭区间[l,r]表示,表示这堆石子是由第l~r堆石子合并而来,重量为a[l]+a[l+1]+……a[r].还有,一定存在一个整数k(l<=k<r),在这堆石子之前先有l~k堆石子被合并成一堆,然后k+1~r堆石子被合并成一堆,然后这两堆石子合并成一堆。
从上述分析,我们就可
以得到一个对应的动态规划的性质,两个长度较小的区间信息向一个更长的区间发生了转移,划分点k就产生了转移决策。自然的,我们要把区间长度len先由小到大枚举(高深点就是把区间长度作为阶段),保证后面的长区间由短区间得到,然后我们应该枚举l(左端点),并找到相应的右端点l+len-1(对应着状态),枚举中间断点k,f[l][r]表示第l堆合并到第r堆合成一堆,需要消耗的最小力气。
对应的状态转移方程: f[l][r]=min(f[l][r],f[l][k]+f[k+1][r])+ a[l]+a[l+1]+……a[r]
a[l]+a[l+1]+……a[r]= sum[r]-sum[l-1])用一个前缀和来维护就行,或许你不知道为什么把l~r的石子连加起来就对了,不是应该两个合并有一个保存值吗?
例如,1,2,3.正确应该为 1+2=3 ,3+3=6,第一次合并3,第二次合并6,答案是九
而上述为什么只计算前缀和1+2+3=6,但你别忘了f[i][j]本身就是有值的,它是有区间长度为2开始一点点推出来的。
状态转移方程 f[l][r]=min(f[l][r],f[l][k]+f[k+1][r])+ sum[r]-sum[l-1])
初值:f[i][i]=0,其余为无穷大
目标:f[1][n]
代码实现:
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
#define mst(a,b) memset((a),(b),sizeof(a))
typedef long long ll;
const int maxn = 205;
const ll mod = 1e9+7;
const ll INF = 1e18;
const double eps = 1e-9;
int n,x;
int sum[maxn];
int dp[maxn][maxn];
int main()
{
while(~scanf("%d",&n))
{
sum[0]=0;
mst(dp,0x3f); //初始化
for(int i=1;i<=n;i++)
{
scanf("%d",&x);
sum[i]=sum[i-1]+x; //前缀和
dp[i][i]=0; //初始化
}
for(int len=2;len<=n;len++) //阶段:区间长度
for(int i=1;i<=n;i++) //状态:左端点
{
int j=i+len-1; //状态:右端点
if(j>n) continue; //决策
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]);
}
}
printf("%d\n",dp[1][n]);
}
return 0;
}
石子合并(二)(环形相邻)
这题与上面有什么不同呢,一个简单例子:这题的最后一个和第一个一开始就能合并,也就是说它的摆放是一个环状,而上面那一题是一排。
由于是环状,我们需要把它转换为链状,那么就把存储石子的数组a[]空间开大一倍,从n+1~2*n存储的值等于1~n存储的值,那么只需要从1到n枚举链的开头即可,链尾则分别为n到2*n-1。这样计算和环状是等效的。解决:
举个例子:
假如现在叫你合并1 2 3,我们就可以写出1 2 3 1 2 3,也就是说把数组扩大成一倍,这样你只要保证你选出来的数是三个就行了,你试试看。
然后你按照上面的思想做就