这两天特别丧,不想学习,心态有点崩。不知道为什么,可能是假期综合症吧,又感觉自己处在了一种极度的迷茫之中,都怪袁老,让我自己一天吃饱了没事干,胡思乱想,呜呜呜.jpg。
区间DP
区间DP就是在区间上的动态规划,求解一段区间上的最优解,通过合并小区间的最优解来得到整个大区间上的最优解的算法。
时间复杂度一般为O(n^2)或者O(n^3),一般做法就是:
- 确定状态,例如初始化等操作。
- 枚举区间长度,枚举区间起始点,有的题目还需要枚举区间断点,从小区间到大区间,合并小区间来得到整个大区间上的最优解。
- dp[1][n]一般就是所求的解。
石子合并问题
很多人一看到石子合并问题可能会想到博弈论,但是博弈论上的一般是先手问题。而石子合并合并的一般是合并相邻两堆石子的数量。石子合并问题有两种玩法。操场玩法和路边玩法,操场玩法就是指把石子围成一堆,路边玩法就是把石子摆成一条直线。
贪心法不能解决,贪心法要求无后效性,但是条件为必须合并相邻两堆这个条件,贪心法无法保证每次都能取到所有堆中石子数最少的两堆。如6 3 4 6 5 4 2贪心法的最小值是62,但是实际上最小值为61,很明显贪心算出来的并不是最小值,贪心法在子过程中得到的解只是局部最优,而不能保证全局的值最优,因此本问题不可以使用贪心法求解。如果使用暴力穷举的办法,会有大量的子问题重复,这种做法会造成效率的低下,所以我们可以考虑动态规划法,是否具有最优子结构性质。
(1)分析最优解的结构特征。
假设已经知道了第k堆石子分开可以得到最优解,那么原问题就变成了两个子问题,子问题分别是{ai,a2,……,ak}和{ak+1,aj}原问题的最优解是否包含子问题的最优解呢?
假设已经知道了n堆石子合并起来的花费是c,子问题1{ai,a2,……,ak}和{ak+1,……,aj}石子合并起来的花费是b,{a1,a2,……,aj}石子数量之和是w(i,j)那么c=a+b+w(i,j).因此我们只需要证明c是最优的,则a和b一定是最优的(即原问题的最优解包含子问题的最优解)。证明用反证法来证。
(2)建立最优值递归式。
设dp[i][j]代表从第i堆到第j堆石子合并的最小花费,dp[i][k]代表从第i堆石子到第k堆石子合并的最小花费,dp[k+1][j]代表从第k+1堆石子到第j堆石子合并的最小花费。w(i,j)代表从第i堆到第j堆的石子数量之和。
dp[i][j]=0 i=j // min(dp[i][k]+dp[k+1][j]+w(i,j) i<j (应该是大括号表示,但是不会用)
题目
石子归并
N堆石子摆成一条线。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的代价。计算将N堆石子合并成一堆的最小代价。
例如: 1 2 3 4,有不少合并方法
1 2 3 4 => 3 3 4(3) => 6 4(9) => 10(19)
1 2 3 4 => 1 5 4(5) => 1 9(14) => 10(24)
1 2 3 4 => 1 2 7(7) => 3 7(10) => 10(20)
括号里面为总代价可以看出,第一种方法的代价最低,现在给出n堆石子的数量,计算最小合并代价。
Input
第1行:N(2 <= N <= 100)
第2 - N + 1:N堆石子的数量(1 <= A[i] <= 10000)
Output
输出最小合并代价
Sample Input
4 1 2 3 4
Sample Output
19
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=1<<30;
const int maxn=40001;
int Min[201][201];
//int Max[maxn][maxn];
int sum[maxn];
int a[maxn];
int main()
{
int n;
while(cin>>n&&n!=0)
{
memset(a,0,sizeof(a));
memset(sum,0,sizeof(sum));
for(int i=1;i<=n;i++)
{
Min[i][i]=0;
// Max[i][i]=0;
}
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
//c=a+b+w(i,j);w(i,j)代表价值和
for(int v=2;v<=n;v++)
{
for(int i=1;i<=n-v+1;i++)
{
int j=i+v-1;
Min[i][j]=INF;
//Max[i][j]=-1;
int tmp=sum[j]-sum[i-1];
for(int k=i;k<j;k++)
{
Min[i][j]=min(Min[i][j],Min[i][k]+Min[k+1][j]+tmp);
// Max[i][j]=max(Max[i][j],Max[i][k]+Max[k+1][j]+tmp);
}
}
}
cout<<Min[1][n]<<endl;
//cout<<Max[1][n]<<endl;
}
return 0;
}
这就是区间dp石子问题的常规解法,如果石子长度不长的话一般直接用dp来做,复杂度为O(n ^ 3), 但是这种方法时间复杂度并不低,在对时间有明显要求的时候这种方法不适合使用,这时候就要用到了一中新算法,叫(GarsiaWachs算法) 能把复杂度降到O(n ^ 2)甚至O(nlogn);
它的步骤如下:
设序列是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]是正无穷的。
举个例子:
假设石子的序列为100,45, 67, 200, 78; 那么第一次找的时候找到了67, 因为200 > 45, 然后合并为 112, 往前遍历,插到第一个比它大的值后面即stone[-1], 然后变成了112, 100, 200, 78, 继续操作, 然后合并112, 100为 212, 序列变为212 200 78, 记得stone[n]为无穷哦, 然后合并200, 78为278, 变为278, 212, 最后在合并为490
所以结果为112 + 212 + 278 + 490 = 1092。这种算法我还不是很会,呜呜呜,等我学会了再说。
圆型石子合并经常转换为直线型来求,也就是说,把圆形结构看成是长度为原规模两倍的直线结构来处理。如果操场玩法(直线玩法)原问题规模为n,所以就相当于有一排石子a1,a2,……,an-1.该问题规模为2*n-1,然后就可以用线性的石子合并问题来解决。